CREATE OR REPLACE NONEDITIONABLE PACKAGE C##CLOUD$SERVICE.dbms_cloud_request AS

  ----------------------------
  --  CONSTANTS AND TYPES
  ----------------------------

  M_IDEN       CONSTANT   PLS_INTEGER := 128;
  M_VCSIZ_4K   CONSTANT   PLS_INTEGER := 4000;
  M_VCSIZ_32K  CONSTANT   PLS_INTEGER := 32767;
  M_QUAL_IDEN  CONSTANT   PLS_INTEGER := 2*M_IDEN+5;

  -- Cloud Store Type integer
  CSTYPE_UNKNOWN          CONSTANT PLS_INTEGER  := 0;
  CSTYPE_ORACLE_BMC       CONSTANT PLS_INTEGER  := 1;
  CSTYPE_ORACLE_BMC_SWIFT CONSTANT PLS_INTEGER  := 2;
  CSTYPE_ORACLE_OSS       CONSTANT PLS_INTEGER  := 3;
  CSTYPE_AMAZON_S3        CONSTANT PLS_INTEGER  := 4;
  CSTYPE_MS_AZURE_BLOB    CONSTANT PLS_INTEGER  := 5;
  CSTYPE_GOOGLE_CLOUD     CONSTANT PLS_INTEGER  := 6;
  CSTYPE_OCI_S3_COMPAT    CONSTANT PLS_INTEGER  := 7;
  CSTYPE_GITHUB           CONSTANT PLS_INTEGER  := 8;
  CSTYPE_BASIC_AUTH       CONSTANT PLS_INTEGER  := 9;

  -- Cloud Store Type string
  CSTYPE_UNKNOWN_STR          CONSTANT DBMS_ID  := 'UNKNOWN';
  CSTYPE_ORACLE_BMC_STR       CONSTANT DBMS_ID  := 'ORACLE_BMC';
  CSTYPE_ORACLE_BMC_SWIFT_STR CONSTANT DBMS_ID  := 'ORACLE_BMC_SWIFT';
  CSTYPE_ORACLE_OSS_STR       CONSTANT DBMS_ID  := 'ORACLE_OSS';
  CSTYPE_AMAZON_STR           CONSTANT DBMS_ID  := 'AMAZON_S3';
  CSTYPE_MS_AZURE_STR         CONSTANT DBMS_ID  := 'MICROSOFT_AZURE_BLOB';
  CSTYPE_GOOGLE_CLOUD_STR     CONSTANT DBMS_ID  := 'GOOGLE_CLOUD_STORAGE';
  CSTYPE_OCI_S3_COMPAT_STR    CONSTANT DBMS_ID  := 'OCI_S3_COMPATIBLE';
  CSTYPE_GITHUB_STR           CONSTANT DBMS_ID  := 'GITHUB';
  CSTYPE_BASIC_AUTH_STR       CONSTANT DBMS_ID  := 'BASIC_AUTH';

  -- Credential type
  CREDTYPE_UNKNOWN        CONSTANT PLS_INTEGER  := 0;
  CREDTYPE_KEY_BASED      CONSTANT PLS_INTEGER  := 1;
  CREDTYPE_PASSWORD_BASED CONSTANT PLS_INTEGER  := 2;

  -- Key String length constant
  KEY_LEN                 CONSTANT PLS_INTEGER       := M_IDEN/2;

  -- HTTP Header Parameter Key strings - Public
  HEADER_RANGE            CONSTANT VARCHAR2(KEY_LEN) := 'Range';

  -- Request Context Type Key strings
  REQUEST_CTX_METHOD      CONSTANT VARCHAR2(KEY_LEN) := 'method';
  REQUEST_CTX_BASEURI     CONSTANT VARCHAR2(KEY_LEN) := 'base_uri';
  REQUEST_CTX_USERURI     CONSTANT VARCHAR2(KEY_LEN) := 'user_uri';
  REQUEST_CTX_HOSTNAME    CONSTANT VARCHAR2(KEY_LEN) := 'host_name';
  REQUEST_CTX_STORETYPE   CONSTANT VARCHAR2(KEY_LEN) := 'store_type';
  REQUEST_CTX_CREDENTIAL  CONSTANT VARCHAR2(KEY_LEN) := 'credential';
  REQUEST_CTX_USERNAME    CONSTANT VARCHAR2(KEY_LEN) := 'username';
  REQUEST_CTX_PASSWORD    CONSTANT VARCHAR2(KEY_LEN) := 'password';
  REQUEST_CTX_KEY         CONSTANT VARCHAR2(KEY_LEN) := 'key';
  REQUEST_CTX_BUCKETURI   CONSTANT VARCHAR2(KEY_LEN) := 'bucketuri';
  REQUEST_CTX_FILEPATH    CONSTANT VARCHAR2(KEY_LEN) := 'filepath';
  REQUEST_CTX_REGION      CONSTANT VARCHAR2(KEY_LEN) := 'region';
  REQUEST_CTX_VERSION     CONSTANT VARCHAR2(KEY_LEN) := 'version';
  REQUEST_CTX_ISPAR       CONSTANT VARCHAR2(KEY_LEN) := 'is_par';
  REQUEST_CTX_RESTAPI     CONSTANT VARCHAR2(KEY_LEN) := 'rest_api_req';
  REQUEST_CTX_CREDTYPE    CONSTANT VARCHAR2(KEY_LEN) := 'credtype';
  REQUEST_CTX_INIT        CONSTANT VARCHAR2(KEY_LEN) := 'initialized';
  REQUEST_CTX_AWSSERVICE  CONSTANT VARCHAR2(KEY_LEN) := 'aws-service';
  REQUEST_CTX_EXPIRATION  CONSTANT VARCHAR2(KEY_LEN) := 'expiration';

  -- URI Query String parameters (keys)
  QSPARAM_COMP            CONSTANT VARCHAR2(KEY_LEN) := 'comp';
  QSPARAM_RESTYPE         CONSTANT VARCHAR2(KEY_LEN) := 'restype';

  -- Associative Array Type for Custom Header parameters (key-value) to make
  -- HTTPS request
  TYPE header_parameters_t IS TABLE OF VARCHAR2(M_VCSIZ_4K)
        INDEX BY VARCHAR2(M_IDEN/2);

  -- Request Context generated by init_request and used by send_request
  TYPE request_context_t IS TABLE OF VARCHAR2(M_VCSIZ_32K)
        INDEX BY VARCHAR2(M_IDEN/2);

  -- URI Query String parameters type table (key-value)
  TYPE uri_query_params_t IS TABLE OF VARCHAR2(M_VCSIZ_4K)
        INDEX BY VARCHAR2(M_IDEN/2);


  ----------------------------
  --  PROCEDURES AND FUNCTIONS
  ----------------------------

  -----------------------------------------------------------------------------
  -- init_request - Initialize HTTPS Request to the Object Store
  -----------------------------------------------------------------------------
  FUNCTION init_request(
    invoker_schema   IN  VARCHAR2,
    credential_name  IN  VARCHAR2,
    base_uri         IN  VARCHAR2,
    method           IN  VARCHAR2,
    operation        IN  VARCHAR2 DEFAULT NULL
  ) RETURN request_context_t;
    --
    -- NAME:
    --   init_request  - Initialize a new HTTP request
    --
    -- DESCRIPTION:
    --   This function initialize UTL_HTTP for a new request.
    --   It sets the wallet path and proxy path in the UTL_HTTP, if required.
    --
    -- PARAMETERS:
    --   invoker_schema   (IN)  -  Invoker schema name
    --
    --   credential_name  (IN)  -  Fully qualified credential name
    --
    --   base_uri         (IN)  -  Base url for the request
    --
    --   method           (IN)  -  REST method type
    --
    --   operation        (IN)  -  Operation type to identify the caller
    --
    -- RETURNS
    --   An opaque context which can be passed to repeated send_request() calls
    --


  -----------------------------------------------------------------------------
  -- send_request - Send an HTTPS Request to the Object Store
  -----------------------------------------------------------------------------
  FUNCTION send_request(
    context   IN OUT request_context_t,
    path      IN VARCHAR2            DEFAULT NULL,
    body      IN BLOB                DEFAULT NULL,
    headers   IN JSON_OBJECT_T       DEFAULT NULL,
    params    IN JSON_OBJECT_T       DEFAULT NULL,
    use_bucket_uri IN BOOLEAN        DEFAULT FALSE,
    bfile_locator  IN BFILE          DEFAULT NULL,
    contents_ptr   IN RAW            DEFAULT NULL,
    contents_len   IN NUMBER         DEFAULT NULL
  ) RETURN UTL_HTTP.resp;
    --
    -- NAME:
    --   send_request  -  Send  HTTP request
    --
    -- DESCRIPTION:
    --   This procedure sends an HTTP request that has been initialized
    --   previously. It accepts optional path argument for specifying the REST
    --   URI parts and also any optional body for the HTTP request.
    --   The response of the request is validated for common HTTP response
    --   codes based on the Cloud Store.
    --
    -- PARAMETERS:
    --   context        (IN)  - request context returned from init_request()
    --   path           (IN)  - REST URL path to concatenate with base URI
    --   body           (IN)  - body of the HTTP Request (used for PUT request)
    --   headers        (IN)  - HTTP header parameters for the request
    --   params         (IN)  - URI query string parameters
    --   use_bucket_uri (IN)  - Use Bucket URI instead of the base URI
    --   bfile_locator  (IN)  - BFILE loc of the file to be put into obj store
    --   contents_ptr   (IN)  - pointer to a C code buffer
    --   contents_len   (IN)  - length of the contents in C code buffer
    --
    -- RETURNS
    --   Response of HTTP request
    --


  -----------------------------------------------------------------------------
  -- end_request - End HTTP request and response
  -----------------------------------------------------------------------------
  PROCEDURE end_request(
    resp    IN OUT  UTL_HTTP.resp
  );
    --
    -- NAME:
    --   end_request  - End HTTP request
    --
    -- DESCRIPTION:
    --   This function ends an HTTP request.
    --
    -- PARAMETERS:
    --
    --   resp             (IN)  -  Response
    --


  -----------------------------------------------------------------------------
  -- assume_role - create temporary credentials by assuming role via AWS STS
  -----------------------------------------------------------------------------
  PROCEDURE assume_role(
    aws_role_arn        IN  VARCHAR2,
    region              IN  VARCHAR2,
    external_id_type    IN  VARCHAR2
  );
    --
    -- NAME:
    --   assume_role - Get AWS temporary credential
    --
    -- DESCRIPTION:
    --   By assuming the role, this function gets a set of temporary
    --   credentials that can be used to access AWS resources.
    --
    -- PARAMETERS:
    --   aws_role_arn      (IN)  - ARN of the AWS IAM role
    --
    --   region            (IN)  - AWS service region
    --
    --   external_id_type  (IN)  - Type of external ID
    --
    -- NOTES:
    --   Added by Bug 32885332.
    --


  -----------------------------------------------------------------------------
  -- get_cloud_store_type - Get cloud store type for a request
  -----------------------------------------------------------------------------
  FUNCTION get_cloud_store_type(
    context    IN  request_context_t
  ) RETURN PLS_INTEGER;
    --
    -- NAME:
    --   get_cloud_store_type  -  Get cloud store type
    --
    -- DESCRIPTION:
    --   This procedure returns the cloud store type from a given request
    --   context. The request context should be already initialized.
    --
    -- PARAMETERS:
    --   context     (IN)     - request context returned from init_request()
    --
    -- RETURNS
    --   Cloud store type as integer (see CSTYPE_* constants)
    --


  -----------------------------------------------------------------------------
  -- get_uri_file_type  -  Get the file path from request uri
  -----------------------------------------------------------------------------
  FUNCTION get_uri_file_path(
    context    IN  request_context_t
  ) RETURN VARCHAR2;
    --
    -- NAME:
    --   get_uri_file_type  -  Get the file path from request uri
    --
    -- DESCRIPTION:
    --   This procedure returns the file path from the request uri stored in
    --   request context passed in.
    --
    -- PARAMETERS:
    --   context     (IN)     - request context returned from init_request()
    --
    -- RETURNS
    --   File path as a string
    --


  -----------------------------------------------------------------------------
  -- clear_cache -  clear the cached credential from request cache
  -----------------------------------------------------------------------------
  PROCEDURE clear_cache;
    --
    -- NAME:
    --   clear_cache -  Clear the cached credentials from request cache
    --
    -- DESCRIPTION:
    --   Clear the cached credentials from request cache to invalidate it.
    --
    -- RETURNS
    --   Nothing
    --


  -----------------------------------------------------------------------------
  -- refresh_resource_principal - Refresh resource principal token
  -----------------------------------------------------------------------------
  PROCEDURE refresh_resource_principal;
    --
    -- NAME:
    --   refresh_resource_principal - Refresh resource principal token
    --
    -- DESCRIPTION:
    --   This is a helper function to refresh resource principal token in
    --   current container.
    --
    -- PARAMETERS:
    --   None.
    --
    -- NOTES:
    --   Added for bug 32729117.
    --


END dbms_cloud_request;  -- End of DBMS_CLOUD_REQUEST Package
/
CREATE OR REPLACE NONEDITIONABLE PACKAGE BODY C##CLOUD$SERVICE.dbms_cloud_request AS


  ----------------------------
  -- Constants
  ----------------------------
  -- Global variable for UTL_HTTP initialization to be done once per-session
  REQUEST_INITIALIZED     BOOLEAN := FALSE;

  -- Maximum length of cloud store URI
  MAX_URI_LEN            CONSTANT PLS_INTEGER       := M_VCSIZ_4K;
  -- Maximum length of AWS ARN
  M_AWS_ROLE_ARN         CONSTANT PLS_INTEGER := 2048;
  -- Maximum length of AWS region
  M_AWS_REGION           CONSTANT PLS_INTEGER := 32;

  -- Maximum retry attempts for HTTP request
  MAX_REQUEST_RETRY      CONSTANT PLS_INTEGER       := 5;

  -- URI scheme
  URI_SCHEME              CONSTANT DBMS_ID     := 'https://';

  -- HTTP Header Parameter Key strings
  HEADER_ACCEPT_ENCODING    CONSTANT VARCHAR2(KEY_LEN) := 'accept-encoding';
  HEADER_CONNECTION         CONSTANT VARCHAR2(KEY_LEN) := 'connection';
  HEADER_CONTENT_TYPE       CONSTANT VARCHAR2(KEY_LEN) := 'Content-Type';
  HEADER_CONTENT_LENGTH     CONSTANT VARCHAR2(KEY_LEN) := 'Content-Length';
  HEADER_CONTENT_LENGTH_AMZ CONSTANT VARCHAR2(KEY_LEN) :=
                                                'X-Amz-Decoded-Content-Length';
  HEADER_CONTENT_SHA256     CONSTANT VARCHAR2(KEY_LEN) :=
                                                'x-content-sha256';
  HEADER_CONTENT_SHA256_AMZ CONSTANT VARCHAR2(KEY_LEN) :=
                                                'X-Amz-Content-SHA256';
  HEADER_CONTENT_ENCODING   CONSTANT VARCHAR2(KEY_LEN) := 'Content-Encoding';
  HEADER_TRANSFER_ENCODING  CONSTANT VARCHAR2(KEY_LEN) := 'Transfer-Encoding';
  HEADER_DATE               CONSTANT VARCHAR2(KEY_LEN) := 'date';
  HEADER_DATE_AMZ           CONSTANT VARCHAR2(KEY_LEN) := 'x-amz-date';
  HEADER_HOST               CONSTANT VARCHAR2(KEY_LEN) := 'host';
  HEADER_CLIENT_INFO        CONSTANT VARCHAR2(KEY_LEN) := 'opc-client-info';
  HEADER_OPC_REQUEST_ID     CONSTANT VARCHAR2(KEY_LEN) := 'opc-request-id';
  HEADER_USER_AGENT         CONSTANT VARCHAR2(KEY_LEN) := 'user-agent';
  HEADER_REQUEST_TARGET     CONSTANT VARCHAR2(KEY_LEN) := '(request-target)';
  HEADER_VERSION_MS         CONSTANT VARCHAR2(KEY_LEN) := 'x-ms-version';
  HEADER_DATE_MS            CONSTANT VARCHAR2(KEY_LEN) := 'x-ms-date';
  HEADER_AUTHORIZATION      CONSTANT VARCHAR2(KEY_LEN) := 'Authorization';
  HEADER_SECURITY_TOKEN_AMZ CONSTANT VARCHAR2(KEY_LEN) :=
                                                 'x-amz-security-token';
  HEADER_ACCEPT             CONSTANT VARCHAR2(KEY_LEN) := 'Accept';
  HEADER_REQUEST_ID         CONSTANT VARCHAR2(KEY_LEN) := 'Request-Id';

  -- HTTP Header Parameter Value strings
  AWS_CHUNKED         CONSTANT VARCHAR2(KEY_LEN) := 'aws-chunked';

  -- Client SDK Identity string
  CLIENT_NAME         CONSTANT VARCHAR2(30) := 'DBMS_CLOUD PLSQL Client/1.0.0';

  -- REST URLs for Oracle Bare Metal Cloud Object Store
  CSURI_AMAZON_S3        CONSTANT DBMS_ID :=
        'amazonaws.com';
  CSURI_ORACLE_OSS       CONSTANT DBMS_ID :=
        'storage.oraclecloud.com';
  CSURI_MS_AZURE_BLOB    CONSTANT DBMS_ID :=
        'blob.core.windows.net';

  -- Pre-authenticated URI delimiter
  BMC_PAR_DELIM          CONSTANT DBMS_ID := '/p/';

  -- Database Property Constants
  PROPERTY_HTTP_PROXY     CONSTANT DBMS_ID := 'HTTP_PROXY';
  PROPERTY_NO_PROXY_DOMS  CONSTANT DBMS_ID := 'HTTP_NO_PROXY_DOMAINS';
  PROPERTY_SSL_WALLET     CONSTANT DBMS_ID := 'SSL_WALLET';
  PROPERTY_DEFAULT_CREDENTIAL CONSTANT DBMS_ID := 'DEFAULT_CREDENTIAL';

  -- Microsoft Azure Blob Storage Service Constants
  MS_DATE_FORMAT     CONSTANT DBMS_ID := 'DY, DD MON YYYY HH24:MI:SS';

  -- Storage Service Constants
  CURRENT_DATE_FORMAT  CONSTANT DBMS_ID := 'YYYY-MM-DD"T"HH24:MI:SSTZH:TZM';

  -- Content Type of JSON body
  APPLICATION_JSON   CONSTANT DBMS_ID := 'application/json';

  --
  -- ARN based authentication
  --
  ARN_CRED_CACHE                    JSON_OBJECT_T := JSON_OBJECT_T('{}');
  TOKEN_EXPIRATION_STR     CONSTANT DBMS_ID       := 'token_expiration';
  SESSION_TOKEN_STR        CONSTANT DBMS_ID       := 'session_token';
  AWS_ARN_CRED             CONSTANT DBMS_ID       := '"AWS$ARN"';
  AWS_STS_VERSION          CONSTANT DBMS_ID       := '2011-06-15';
  AWS_MAX_SESSION_DURATION CONSTANT NUMBER        := 15*60;
  AWS_DEFAULT_REGION       CONSTANT DBMS_ID       := 'us-east-1';

  -- Passwordless authentication properties
  REQ_PROPERTY_AWSROLEARN         CONSTANT DBMS_ID    := 'aws_role_arn';
  REQ_PROPERTY_EXTERNALIDTYPE     CONSTANT DBMS_ID    := 'external_id_type';
  REQ_PROPERTY_GCPPA              CONSTANT DBMS_ID    := 'gcp_pa';

  -- AWS strings
  AWS_SERVICE_STS          CONSTANT DBMS_ID       := 'sts';
  AWS_SERVICE_S3           CONSTANT DBMS_ID       := 's3';

  -- Broker authentication
  CSTYPE_BROKER            CONSTANT PLS_INTEGER   := -1;
  BROKER_CRED_CACHE                 JSON_OBJECT_T := JSON_OBJECT_T('{}');
  BROKER_TOKEN             CONSTANT DBMS_ID       := 'token';

  -- Send_request operation types
  OPERATION_BROKER         CONSTANT DBMS_ID       := 'broker';

  -- Github constants
  GITHUB_ACCEPT         CONSTANT DBMS_ID   := 'application/vnd.github.v3+json';

  --
  -- Session cache for Request context
  --
  CACHED_REQUEST_CTX  request_context_t;
  -- 15min expiry of cache context
  CACHED_REQUEST_CTX_EXPIRATION   CONSTANT PLS_INTEGER := 15;

  --
  -- Exceptions
  --
  cred_not_exist EXCEPTION;
  PRAGMA EXCEPTION_INIT(cred_not_exist, -27476);



  -----------------------------------------------------------------------------
  --           FORWARD DECLARATIONS (STATIC FUNCTION/PROCEDURE DECLARATIONS)
  -----------------------------------------------------------------------------

  -----------------------------------------------------------------------------
  -- extract_cloud_store_type - Extract Cloud Store Type from the URI
  -----------------------------------------------------------------------------
  FUNCTION extract_cloud_store_type(
    uri       IN  VARCHAR2,
    version   OUT VARCHAR2,
    operation IN  VARCHAR2
  ) RETURN PLS_INTEGER;
    --
    -- NAME:
    --   extract_cloud_store_type - Extract Cloud Store Type From the URI
    --
    -- DESCRIPTION:
    --   This procedure obtains the type of cloud store from the base uri.
    --
    -- PARAMETERS:
    --   uri      (IN)   - The base URI for the request
    --   version  (OUT)  - version of the object store REST API
    --
    --
    -- RETURNS:
    --   The cloud store type constant
    --
    -- NOTES:
    --


  -----------------------------------------------------------------------------
  -- parse_credential_name - Parse a qualified credential name
  -----------------------------------------------------------------------------
  PROCEDURE parse_credential_name(
     qualified_name        IN   VARCHAR2,
     owner_name            OUT  VARCHAR2,
     credential_name       OUT  VARCHAR2
  )
  IS
    --
    -- NAME:
    --   parse_credential_name - Parse a qualified credential name
    --
    -- DESCRIPTION:
    --   This procedure parses a qualified credential name and returns the
    --   credential owner and credential name.
    --
    -- PARAMETERS:
    --   qualified_name       (IN)   - Qualified credential name string
    --
    --   owner_name           (OUT)  - Owner of the credential object
    --
    --   credential_name      (OUT)  - Credential object name
    --
    LANGUAGE C
    LIBRARY sys.dbms_pdb_lib
    NAME "kpdbocParseCred" WITH CONTEXT
    PARAMETERS (CONTEXT,
                qualified_name   OCISTRING, qualified_name  INDICATOR SB2,
                owner_name       OCISTRING, owner_name      INDICATOR SB2,
                credential_name  OCISTRING, credential_name INDICATOR SB2);


  -----------------------------------------------------------------------------
  -- get_qualified_credential - Get qualified credential name
  -----------------------------------------------------------------------------
  FUNCTION get_qualified_credential(
     credential_name   IN  VARCHAR2,
     invoker_name      IN  VARCHAR2
  ) RETURN VARCHAR2
  IS
    --
    -- NAME:
    --   get_qualified_credential - Get qualified credential name
    --
    -- DESCRIPTION:
    --   This function returns a qualified name for a credential object by
    --   concatenating the schema name and object name.
    --
    -- PARAMETERS:
    --   credential_name   (IN)  - Name of the credential object
    --
    --   invoker_name      (IN)  - Name of the invoker user
    --
    -- RETURNS:
    --   Qualified object name with schema name concatenated.
    --
    -- NOTES:
    --
  BEGIN

    -- Bug 29128159: remove default_credential support

    -- Bug 28924601: allow NULL credential for pre-auth URLs
    IF credential_name IS NULL THEN
      RETURN NULL;
    END IF;

    RETURN DBMS_CLOUD_CORE.get_qualified_name(credential_name, invoker_name,
                                             DBMS_CLOUD_CORE.ASSERT_TYPE_CRED);
  END get_qualified_credential;


  -----------------------------------------------------------------------------
  -- get_default_credential - Get default credential in the database
  -----------------------------------------------------------------------------
  FUNCTION get_default_credential(
     invoker_schema    IN   VARCHAR2
  ) RETURN VARCHAR2
  IS
    --
    -- NAME:
    --   get_default_credential - Get default credential
    --
    -- DESCRIPTION:
    --   This function returns the default credential if it is owned by
    --   the invoker schema.
    --
    -- PARAMETERS:
    --   credential_name   (IN)  - Name of the credential object
    --
    --   invoker_name      (IN)  - Name of the invoker user
    --
    -- RETURNS:
    --   Qualified object name with schema name concatenated.
    --
    -- NOTES:
    --
    l_owner                 DBMS_QUOTED_ID;
    l_credential_name       DBMS_QUOTED_ID;
    l_qualified_credential  VARCHAR2(M_QUAL_IDEN);
  BEGIN
    -- If no credential name is passed in, then we can use DEFAULT_CREDENTIAL
    -- provided the default credential is owned by the current invoker.
    DBMS_CLOUD_CORE.get_db_property(PROPERTY_DEFAULT_CREDENTIAL,
                                    l_qualified_credential, FALSE);

    -- Parse the credential and enquote the names without upper casing
    parse_credential_name(l_qualified_credential, l_owner, l_credential_name);
    l_owner := DBMS_ASSERT.enquote_name(l_owner, FALSE);
    l_credential_name := DBMS_ASSERT.enquote_name(l_credential_name, FALSE);

    -- Credential owner must be same as the current invoker
    IF invoker_schema = l_owner THEN
      RETURN DBMS_CLOUD_CORE.get_qualified_name(l_credential_name, l_owner,
                                             DBMS_CLOUD_CORE.ASSERT_TYPE_CRED);
    END IF;

    RETURN NULL;

  EXCEPTION
    WHEN OTHERS THEN
      RETURN NULL;
  END get_default_credential;


  -----------------------------------------------------------------------------
  -- convert_swift_to_oci_uri - Converts Swift URI to OCI Console URI
  -----------------------------------------------------------------------------
  FUNCTION convert_swift_to_oci_uri(
    uri         IN OUT VARCHAR2,
    version     IN     VARCHAR2
  ) RETURN BOOLEAN
  IS
    --
    -- NAME:
    --   convert_swift_to_oci_uri  - Converts Swift URI to OCI Console URI
    --
    -- DESCRIPTION:
    --   This procedure checks if the uri provided is a Swift URI and
    --   then converts it to a OCI Console URI
    --
    -- PARAMETERS:
    --   uri              (IN OUT)  -  uri for the request
    --   version          (IN)      -  version of cloud store type
    --
    -- RETURNS
    --   TRUE, if the conversion is successful otherwise FALSE
    --
    -- Example:
    -- swift object store uri:
    -- 'https://swiftobjectstorage.us-phoenix-1.oraclecloud.com/v1/tenant1/bucket1/test1.csv'
    --
    -- oci console uri:
    -- 'https://objectstorage.us-phoenix-1.oraclecloud.com/n/tenant1/b/bucket1/o/test1.csv'
    --
    --
    l_version               VARCHAR2(MAX_URI_LEN);
    l_namespace             VARCHAR2(MAX_URI_LEN);
    l_bucket                VARCHAR2(MAX_URI_LEN);
    l_object                VARCHAR2(MAX_URI_LEN);
    l_namespace_pos         NUMBER;
    l_bucket_pos            NUMBER;
    l_object_pos            NUMBER;
    l_version_pos           NUMBER;
    l_swift_domain_pos      NUMBER;
    BMC_NAMESPACE_DELIM     VARCHAR2(4) := '/n/';
    BMC_BUCKET_DELIM        VARCHAR2(4) := '/b/';
    BMC_OBJECT_DELIM        VARCHAR2(4) := '/o/';
  BEGIN

    IF (uri IS NULL) OR (length(uri) = 0) THEN
      RETURN FALSE;
    END IF;

    -- Check if uri is a swift object store URI
    -- Bug 30828243: support gov region domains
    l_swift_domain_pos := REGEXP_INSTR(uri,
                            'swiftobjectstorage\.(.*)\.oracle',
                            1, 1, 0, 'i');
    IF l_swift_domain_pos = 0 THEN
      RETURN FALSE;
    END IF;

    -- Get the positions of version, namespace, bucket and the object
    l_version_pos   := INSTR(uri, '/', 1, 3);
    l_namespace_pos := INSTR(uri, '/', 1, 4);
    l_bucket_pos    := INSTR(uri, '/', 1, 5);
    l_object_pos    := INSTR(uri, '/', 1, 6);

    -- If namespace and bucket are not found then return
    -- Note that object position can be null if the operation being
    -- performed is on a bucket.
    IF (l_namespace_pos = 0) OR (l_bucket_pos = 0) THEN
      RETURN FALSE;
    END IF;

    -- Get the version from the URI
    l_version := SUBSTR(uri, l_version_pos + 1,
                        l_namespace_pos - l_version_pos - 1);

    -- the version in the URI should match the version supported by
    -- Swift API
    -- Bug 29995958: Check for NULL version
    IF l_version IS NULL OR version != l_version THEN
      RETURN FALSE;
    END IF;

    -- Get the namespace
    l_namespace := SUBSTR(uri, l_namespace_pos + 1,
                          l_bucket_pos - l_namespace_pos - 1);

    -- Get the bucket name and the object name
    IF (l_object_pos = 0) THEN
      l_bucket  := SUBSTR(uri, l_bucket_pos + 1);
    ELSE
      l_bucket  := SUBSTR(uri, l_bucket_pos + 1,
                          l_object_pos - l_bucket_pos - 1);
      l_object  := SUBSTR(uri, l_object_pos + 1);
    END IF;

    uri := SUBSTR(uri, 0, l_swift_domain_pos - 1) ||
             SUBSTR(uri, l_swift_domain_pos + 5,
                    l_version_pos - l_swift_domain_pos - 5) ||
             BMC_NAMESPACE_DELIM || l_namespace ||
             BMC_BUCKET_DELIM || l_bucket || BMC_OBJECT_DELIM;
    IF l_object_pos != 0 THEN
      uri := uri || l_object;
    END IF;

    RETURN TRUE;

  END convert_swift_to_oci_uri;


  -----------------------------------------------------------------------------
  -- convert_oci_to_swift_uri - Converts OCI Console URI to Swift URI
  -----------------------------------------------------------------------------
  FUNCTION convert_oci_to_swift_uri(
    uri         IN OUT VARCHAR2
  ) RETURN BOOLEAN
  IS
    --
    -- NAME:
    --   convert_oci_to_swift_uri  - Convert OCI URI to Swift URI
    --
    -- DESCRIPTION:
    --   This procedure checks if the uri provided is a OCI console URI and
    --   then converts it to a swift object store URI.
    --
    -- PARAMETERS:
    --   uri              (IN OUT)  -  uri for the request
    --
    -- RETURNS
    --   TRUE, if the conversion is successful otherwise FALSE
    --
    -- Example:
    -- oci console uri:
    -- 'https://objectstorage.us-phoenix-1.oraclecloud.com/n/tenant1/b/bucket1/o/test1.csv'
    --
    -- swift object store uri:
    -- 'https://swiftobjectstorage.us-phoenix-1.oraclecloud.com/v1/tenant1/bucket1/test1.csv'
    --
    l_namespace             VARCHAR2(MAX_URI_LEN);
    l_bucket                VARCHAR2(MAX_URI_LEN);
    l_object                VARCHAR2(MAX_URI_LEN);
    l_version               VARCHAR2(MAX_URI_LEN);
    l_hostname              VARCHAR2(MAX_URI_LEN);
    l_namespace_pos         NUMBER;
    l_bucket_pos            NUMBER;
    l_object_pos            NUMBER;
    l_oci_domain_pos        NUMBER;
    l_swift_domain_pos      NUMBER;
    BMC_NAMESPACE_DELIM     VARCHAR2(4) := '/n/';
    BMC_BUCKET_DELIM        VARCHAR2(4) := '/b/';
    BMC_OBJECT_DELIM        VARCHAR2(4) := '/o/';
    l_bmc_namespace_delim_len NUMBER;
    l_bmc_bucket_delim_len    NUMBER;
    l_bmc_object_delim_len    NUMBER;
    l_cloud_store             PLS_INTEGER;

  BEGIN

    -- Check for empty URL
    IF uri IS NULL OR LENGTH(uri) = 0 THEN
      RETURN FALSE;
    END IF;

    -- Check if uri is a swift object store URI and return if it is
    -- Bug 30828243: support gov region domains
    l_swift_domain_pos := REGEXP_INSTR(uri,
                           'swiftobjectstorage\.(.*)\.oracle',
                            1, 1, 0, 'i');
    IF l_swift_domain_pos != 0 THEN
      RETURN FALSE;
    END IF;

    -- Check if uri is OCI Native URI
    -- Bug 29749608: OCI Native URI must contain namespace and bucket name,
    -- object name is optional.
    -- Bug 30828243: support gov region domains
    l_oci_domain_pos := REGEXP_INSTR(uri,
                         'objectstorage\.(.*)\.oracle.*' ||
                         BMC_NAMESPACE_DELIM || '(.*)' ||
                         BMC_BUCKET_DELIM || '(.*)',
                          1, 1, 0, 'i');
    IF l_oci_domain_pos = 0 THEN
      RETURN FALSE;
    END IF;

    -- Get namespace position (mandatory)
    l_namespace_pos := INSTR(uri, BMC_NAMESPACE_DELIM);
    IF l_namespace_pos = 0 THEN
      RETURN FALSE;
    END IF;
    l_bmc_namespace_delim_len := LENGTH(BMC_NAMESPACE_DELIM);

    -- Get bucket position (mandatory)
    l_bucket_pos := INSTR(uri, BMC_BUCKET_DELIM,
                          l_namespace_pos + l_bmc_namespace_delim_len);
    IF l_bucket_pos = 0 THEN
      RETURN FALSE;
    END IF;
    l_bmc_bucket_delim_len := LENGTH(BMC_BUCKET_DELIM);

    -- Get object name position (optional)
    l_object_pos := INSTR(uri, BMC_OBJECT_DELIM,
                          l_bucket_pos + l_bmc_bucket_delim_len);
    l_bmc_object_delim_len := LENGTH(BMC_OBJECT_DELIM);

    -- Check if namespace, bucket and object are of non zero length.
    -- Object name is optional
    IF ((l_object_pos > 0 AND
         l_object_pos <= l_bucket_pos + l_bmc_bucket_delim_len) OR
        (l_bucket_pos <= l_namespace_pos + l_bmc_object_delim_len)) THEN
      RETURN FALSE;
    END IF;

    -- Get namespace value
    l_namespace := SUBSTR(uri, l_namespace_pos +  l_bmc_namespace_delim_len,
                  l_bucket_pos - l_namespace_pos -  l_bmc_namespace_delim_len);

    -- Get bucket value
    IF l_object_pos > 0 THEN
      l_bucket := SUBSTR(uri, l_bucket_pos + l_bmc_bucket_delim_len,
                         l_object_pos - l_bucket_pos - l_bmc_bucket_delim_len);
    ELSE
      l_bucket := SUBSTR(uri, l_bucket_pos + l_bmc_bucket_delim_len);
    END IF;

    -- Get object value
    IF l_object_pos > 0 THEN
      l_object := SUBSTR(uri, l_object_pos + l_bmc_object_delim_len);
    END IF;

    -- Bug 29749523: Bucket name in native uri can end with '/o' optionally
    IF SUBSTR(l_bucket, -2) = '/o' THEN
      l_bucket := SUBSTR(l_bucket, 1, LENGTH(l_bucket)-1);
    END IF;

    -- Bug 29749523: Bucket cannot have '/' character, except as last character
    IF INSTR(l_bucket, '/', -2) > 0 THEN
      RETURN FALSE;
    END IF;

    -- Construct the swift uri
    uri := SUBSTR(uri, 0, l_oci_domain_pos - 1) || 'swift' ||
             SUBSTR(uri, l_oci_domain_pos,
                    l_namespace_pos - l_oci_domain_pos + 1);

    -- Host Name
    l_hostname := SUBSTR(REGEXP_REPLACE(uri,
                               '(https?:\/\/[-0-9a-z.]*)(/|.*)$',
                               '\1', 1, 1, 'in'),
                         LENGTH(URI_SCHEME)+1);

    -- Get the current version supported by Swift API
    l_cloud_store := extract_cloud_store_type(l_hostname, l_version, NULL);

    uri := uri || l_version || '/' || l_namespace || '/' || l_bucket;
    IF l_object_pos > 0 THEN
      uri := uri || '/' || l_object;
    END IF;

    RETURN TRUE;

  END convert_oci_to_swift_uri;


  -----------------------------------------------------------------------------
  -- send_request_headers - Set the HTTPS Request Header Parameters
  -----------------------------------------------------------------------------
  PROCEDURE set_request_headers(
    req        IN OUT  UTL_HTTP.req,
    headers    IN      JSON_OBJECT_T
  )
  IS
    --
    -- NAME:
    --   set_request_headers - Set the HTTPS Request Header Parameters
    --
    -- DESCRIPTION:
    --   This function sets the HTTPS request header parameters on the given
    --   request object, using the array of key-value parameters.
    --
    -- PARAMETERS:
    --   req      (IN OUT) - UTL_HTTP request object
    --
    --   headers  (IN)     - associate array of key-value header parameters
    --
    l_keys  JSON_KEY_LIST;
    l_key   VARCHAR2(KEY_LEN);
  BEGIN

    -- Set all headers from the JSON object
    l_keys := headers.get_keys();
    IF l_keys IS NOT NULL THEN
      FOR l_idx IN 1..l_keys.COUNT
      LOOP
        l_key := l_keys(l_idx);
        UTL_HTTP.set_header(req, l_key, headers.get_string(l_key));
      END LOOP;
    END IF;

  END set_request_headers;


  -----------------------------------------------------------------------------
  -- is_pre_authenticated_uri  - Is Pre-Authenticated URI
  -----------------------------------------------------------------------------
  FUNCTION is_pre_authenticated_uri(
    request_uri IN VARCHAR2,
    domain_name IN VARCHAR2,
    store_type  IN VARCHAR2
  ) RETURN BOOLEAN
  IS
    --
    -- NAME:
    --   is_pre_authenticated_uri - Is Pre-Authenticated URI.
    --
    -- DESCRIPTION:
    --   If the input URI looks like a pre_authenticated URI, then it returns
    --   TRUE otherwise FALSE
    --
    -- PARAMETERS:
    --   request_uri (IN) - URI that needs to be checked.
    --   domain_name (IN) - domain name of the request URI
    --   store_type  (IN) - cloud store type
    --
    -- RETURNS:
    --   TRUE  - If it is a pre-authenticated URI
    --   FALSE - If it is not a pre-authenticated URI
    --
    -- NOTES:
    --
    l_is_par BOOLEAN := FALSE;
  BEGIN

    IF store_type = CSTYPE_ORACLE_BMC THEN
      -- For Oracle BMC, look for /p/ in the URI right after the domain name
      -- to distingush Pre-Authenticated URI from regular URI
      IF LENGTH(domain_name) + 1 = INSTR(request_uri, BMC_PAR_DELIM) THEN
        l_is_par := TRUE;
      END IF;
    END IF;

    RETURN l_is_par;

  END is_pre_authenticated_uri;


  -----------------------------------------------------------------------------
  -- extract_cloud_store_type - Extract Cloud Store Type from the URI
  -----------------------------------------------------------------------------
  FUNCTION extract_cloud_store_type(
    uri       IN  VARCHAR2,
    version   OUT VARCHAR2,
    operation IN  VARCHAR2
  ) RETURN PLS_INTEGER
  IS
    l_cloud_type    dbms_cloud_store.cloud_type%TYPE;
    -- Bug 31463476: support s3 compatible URL for any object store.
    l_store_type    PLS_INTEGER := CSTYPE_AMAZON_S3;
  BEGIN
    -- If uri is NULL or empty, return unknown cloud store type.
    IF uri IS NULL THEN
      RETURN CSTYPE_UNKNOWN;
    END IF;

    -- Bug 32729117: If internal broker request, return without parsing URL
    IF operation = OPERATION_BROKER THEN
      RETURN CSTYPE_BROKER;
    END IF;

    -- Get the cloud type from dbms_cloud_store corresponding to the input URI.
    -- Bug 30656686: It is possible to have multiple matching patterns.
    -- especially for Oracle Cloud. Fetch the row with longest pattern
    -- matching the input URI.
    SELECT cloud_type, NVL(version, '') INTO l_cloud_type, version
      FROM dbms_cloud_store cs
      WHERE uri LIKE cs.base_uri_pattern AND cs.status = 1
      ORDER BY LENGTH(cs.base_uri_pattern) DESC
      FETCH FIRST 1 ROW ONLY;

    CASE
      WHEN l_cloud_type = CSTYPE_ORACLE_BMC_STR THEN
        l_store_type := CSTYPE_ORACLE_BMC;
      WHEN l_cloud_type = CSTYPE_ORACLE_BMC_SWIFT_STR THEN
        l_store_type := CSTYPE_ORACLE_BMC_SWIFT;
      WHEN l_cloud_type = CSTYPE_AMAZON_STR THEN
        l_store_type := CSTYPE_AMAZON_S3;
      WHEN l_cloud_type = CSTYPE_ORACLE_OSS_STR THEN
        l_store_type := CSTYPE_ORACLE_OSS;
      WHEN l_cloud_type = CSTYPE_MS_AZURE_STR THEN
        l_store_type := CSTYPE_MS_AZURE_BLOB;
      WHEN l_cloud_type = CSTYPE_GOOGLE_CLOUD_STR THEN
        l_store_type := CSTYPE_GOOGLE_CLOUD;
      WHEN l_cloud_type = CSTYPE_OCI_S3_COMPAT_STR THEN
        l_store_type := CSTYPE_OCI_S3_COMPAT;
      WHEN l_cloud_type = 'GITHUB' THEN
        l_store_type := 8;
      WHEN l_cloud_type = 'BASIC_AUTH' THEN
        l_store_type := 9;
      ELSE
        NULL;
    END CASE;

    RETURN l_store_type;
  EXCEPTION
    -- If base_uri doesn't match with any of the supported cloud stores
    WHEN NO_DATA_FOUND THEN
      RETURN l_store_type;
    WHEN OTHERS THEN
      RAISE;
  END extract_cloud_store_type;


  -----------------------------------------------------------------------------
  -- convert_uri - Converts uri to type that works with credential provided
  -----------------------------------------------------------------------------
  PROCEDURE convert_uri(
    request_uri       IN OUT VARCHAR2,
    store_type        IN OUT VARCHAR2,
    version           IN     VARCHAR2,
    cred_type         IN     NUMBER DEFAULT CREDTYPE_UNKNOWN
  )
  IS
    --
    -- NAME:
    -- convert_uri - Converts uri to the type that works with credential
    --               provided
    --
    -- DESCRIPTION:
    --   This procedure converts the uri provided to the type that is suitable
    --   to use with the credential provided.
    --
    -- PARAMETERS:
    --   request_uri              (IN OUT)  -  uri for the request
    --   store_type               (IN OUT)  -  cloud store type
    --   version                  (IN)      -  version of cloud store
    --   cred_type                (IN)      -  credential type
    --
    -- RETURNS
    --   NONE
    --
    -- Example:
    -- request_uri:
    -- 'https://objectstorage.us-phoenix-1.oraclecloud.com/n/tenant1/b/bucket1/o/test1.csv'
    --
    -- cred_type:
    -- CSTYPE_ORACLE_BMC_SWIFT
    --
    -- converted_uri:
    -- 'https://swiftobjectstorage.us-phoenix-1.oraclecloud.com/v1/tenant1/bucket1/test1.csv'
    --
    l_invalid_uri   BOOLEAN := FALSE;
  BEGIN

    CASE store_type
      WHEN CSTYPE_ORACLE_BMC THEN
        CASE cred_type
          WHEN CREDTYPE_PASSWORD_BASED THEN
            -- Convert the OCI native URI to swift based URI
            IF convert_oci_to_swift_uri(request_uri) = TRUE THEN
              store_type := CSTYPE_ORACLE_BMC_SWIFT;
            ELSE
              l_invalid_uri := TRUE;
            END IF;
          ELSE
            NULL;
        END CASE;
      WHEN CSTYPE_ORACLE_BMC_SWIFT THEN
        CASE cred_type
          WHEN CREDTYPE_KEY_BASED THEN
            -- Convert the swift based URI to OCI native URI
            IF convert_swift_to_oci_uri(request_uri, version) = TRUE THEN
              store_type := CSTYPE_ORACLE_BMC;
            ELSE
              l_invalid_uri := TRUE;
            END IF;
          ELSE
            NULL;
        END CASE;
      ELSE
        NULL;
    END CASE;

    IF l_invalid_uri = TRUE THEN
      raise_application_error(DBMS_CLOUD.EXCP_INVALID_OBJ_URI,
                'Invalid object uri - ' || request_uri);
    END IF;

  END convert_uri;


  -----------------------------------------------------------------------------
  -- rest_api_request - Rest API request
  -----------------------------------------------------------------------------
  FUNCTION rest_api_request
  RETURN BOOLEAN
  IS
  BEGIN
    IF INSTR(DBMS_UTILITY.FORMAT_CALL_STACK,
             SYS_CONTEXT('USERENV', 'CURRENT_USER') ||
             '.DBMS_CLOUD.SEND_REQUEST' || CHR(10)) > 0
    THEN
      RETURN TRUE;
    END IF;

    RETURN FALSE;
  END rest_api_request;


  -----------------------------------------------------------------------------
  -- pre_parse_request_uri - Pre-Parse the Request URI
  -----------------------------------------------------------------------------
  PROCEDURE pre_parse_request_uri(
    uri             IN  VARCHAR2,
    operation       IN  VARCHAR2,
    request_uri     OUT VARCHAR2,
    host_name       OUT VARCHAR2,
    store_type      OUT VARCHAR2,
    version         OUT VARCHAR2,
    domain_name     OUT VARCHAR2,
    is_par          OUT BOOLEAN
  )
    --
    -- NAME:
    --   pre_parse_request_uri - Pre-parse the request UTI
    --
    -- DESCRIPTION:
    --   This procedure parses the request URI and assigns the sanitized
    --   URI, Host name and Cloud Store type.
    --   It also determines if the URI is a Pre-Auth URI
    --
    -- PARAMETERS:
    --   uri              (IN)  - The input URI
    --   request_uri      (OUT) - The request URI after sanitization
    --   host_name        (OUT) - The host name from the URI
    --   store_type       (OUT) - The cloud store type
    --   version          (OUT) - Object store REST API version (only MS Azure)
    --   domain_name      (OUT) - The domain of the URI
    --   is_par           (OUT) - indicates if the URI resembles a PAR
    --
    -- NOTES:
    --
  IS
    l_slash_pos    PLS_INTEGER;
  BEGIN

    -- Bug 27139558: input URI will be a qualified URI
    request_uri := uri;

    -- Bug 25798797:
    -- Get the domain name part of the URI and convert to Lower Case
    -- According to RFC 4343, domain names are always case insensitive.

    -- Domain Name
    -- Bug 32843944: Add "n" in the matching parameters to detect the newline
    -- character.
    l_slash_pos := INSTR(request_uri, '/', 1, 3);
    IF l_slash_pos > 0 THEN
      domain_name := LOWER(SUBSTR(request_uri, 1, l_slash_pos - 1));
    ELSE
      domain_name := LOWER(request_uri);
    END IF;

    -- Bug 32885290: validate domain name does not have special characters
    IF  REGEXP_REPLACE(domain_name, '(https?:\/\/[-0-9a-z.]*)', '',
                       1, 1, 'in') IS NOT NULL
    THEN
       raise_application_error(DBMS_CLOUD.EXCP_UNSUPP_OBJ_STORE,
             'Unsupported object store URI - ' || uri);
    END IF;

    -- Sanitized Request URI
    request_uri := domain_name ||
                   SUBSTR(request_uri, LENGTH(domain_name)+1,
                          LENGTH(request_uri));

    -- Host Name
    -- Bug 32843944: Add "n" in the matching parameters to detect the newline
    -- character.
    host_name := SUBSTR(domain_name, LENGTH(URI_SCHEME)+1);

    -- Cloud Store Type
    store_type  := extract_cloud_store_type(host_name, version, operation);

    -- Pre-Authenticated URI
    is_par := is_pre_authenticated_uri(request_uri, domain_name, store_type);

  END pre_parse_request_uri;

  -----------------------------------------------------------------------------
  -- parse_request_uri - Parse the Request URI
  -----------------------------------------------------------------------------
  PROCEDURE parse_request_uri(
    cred_type       IN     PLS_INTEGER,
    is_par          IN     BOOLEAN,
    rest_api_req    IN     BOOLEAN,
    uri             IN     VARCHAR2,
    request_uri     IN OUT VARCHAR2,
    host_name       IN     VARCHAR2,
    store_type      IN OUT VARCHAR2,
    version         IN     VARCHAR2,
    domain_name     IN     VARCHAR2,
    bucket_uri      OUT    VARCHAR2,
    file_path       OUT    VARCHAR2,
    region          OUT    VARCHAR2,
    aws_service     OUT    VARCHAR2
  )
    --
    -- NAME:
    --   parse_request_uri - Validate the Request URI
    --
    -- DESCRIPTION:
    --   This procedure parses the request URI and assigns the sanitized
    --   URI, Host name and Cloud Store type.
    --
    -- PARAMETERS:
    --   cred_type     (IN)     - The credential type
    --   is_par        (IN)     - indicates if the URI resembles a PAR
    --   rest_api_req  (IN)     - REST API request from dbms_cloud.send_request
    --   uri           (IN)     - original request URI
    --   request_uri   (IN OUT) - The request URI after sanitization
    --   host_name     (IN)     - The host name from the URI
    --   store_type    (IN OUT) - The cloud store type
    --   version       (IN)     - version of object store
    --   domain_name   (IN)     - Domain name
    --   bucket_uri    (OUT)    - The bucket URI in the input URI
    --   file_path     (OUT)    - The file path in the input URI
    --   region        (OUT)    - Object store region (only set for Amazon S3)
    --
    -- NOTES:
    --
  IS
    l_index           INTEGER;
    l_version         VARCHAR2(MAX_URI_LEN);
    l_version_pos     NUMBER;
    l_namespace_pos   NUMBER;
    l_file_path_len   NUMBER;
    l_amazonaws_pos   PLS_INTEGER;
    l_temp_buf        VARCHAR2(MAX_URI_LEN);
  BEGIN

    -- Convert the URI only if it is not a pre-authenticated URI
    -- Bug 30828243: do not convert URI for send_request REST API
    IF NOT is_par AND NOT rest_api_req THEN
      -- Bug 28900393: Support OCI console URI
      convert_uri(request_uri, store_type, version, cred_type);
    END IF;

    IF store_type = CSTYPE_UNKNOWN THEN
      raise_application_error(DBMS_CLOUD.EXCP_UNSUPP_OBJ_STORE,
               'Unsupported object store URI - ' || uri);
    END IF;

    -- Bucket URI
    IF store_type = CSTYPE_AMAZON_S3 THEN
      -- For Amazon REST API, get the AWS Region and Bucket URL
      -- The hostname format for AWS is:
      --    <optional-prefix>.<aws-service>.<aws-region>.<awsdomain>.com
      -- If no region is found, then assume default aws region.
      -- Bug 31461405: Add a check to distinguish between Amazon S3 URLs and
      -- S3 compatible URLs by searching for CSURI_AMAZON_S3. For S3 compatible
      -- URLs, we leave the region blank. For S3 URLs, we extract region from
      -- the host name.
      -- Bug 32306175: Set the corresponding service name for AWS STS requests.
      --
      l_amazonaws_pos := INSTR(host_name, CSURI_AMAZON_S3);
      IF l_amazonaws_pos > 0 THEN
        -- Remove the <awsdomain>.com string from host name
        l_temp_buf := REPLACE(host_name, '.' || CSURI_AMAZON_S3);

        -- Optimistically assume that the last part of the buffer is region
        region := SUBSTR(l_temp_buf, INSTR(l_temp_buf, '.', -1)+1);
        IF region IS NULL THEN
          raise_application_error(DBMS_CLOUD.EXCP_UNSUPP_OBJ_STORE,
                 'Unsupported object store URI - ' || uri);
        END IF;

        IF INSTR(region, '-') = 0 THEN
          -- Region must contain a hyphen (-), otherwise region is missing
          -- and use default region
          aws_service := region;
          region := AWS_DEFAULT_REGION;
        ELSE
          -- Remove <aws-region> from the buffer
          l_temp_buf := SUBSTR(l_temp_buf, 1, INSTR(l_temp_buf, '.', -1)-1);
          aws_service := SUBSTR(l_temp_buf, INSTR(l_temp_buf, '.', -1)+1);
        END IF;

        -- Validate AWS region length
        IF LENGTH(region) > M_AWS_REGION THEN
          raise_application_error(DBMS_CLOUD.EXCP_UNSUPP_OBJ_STORE,
                 'Unsupported object store URI - ' || uri);
        END IF;
      END IF;

      -- Bug 27598411: Bucket URI must always end with '/' for S3 requests
      IF aws_service = AWS_SERVICE_S3 AND
         INSTR(host_name, aws_service) = 1
      THEN
        bucket_uri := SUBSTR(request_uri, LENGTH(domain_name)+2);
        bucket_uri := domain_name || '/' ||
                      NVL(SUBSTR(bucket_uri, 1, INSTR(bucket_uri, '/')),
                          bucket_uri || '/');
      ELSE
        bucket_uri := domain_name || '/';
      END IF;

    ELSIF store_type = CSTYPE_ORACLE_BMC_SWIFT OR
          store_type = CSTYPE_ORACLE_OSS THEN
      IF store_type = CSTYPE_ORACLE_BMC_SWIFT THEN
        -- Get the positions of version, namespace
        l_version_pos   := INSTR(request_uri, '/', 1, 3);
        l_namespace_pos := INSTR(request_uri, '/', 1, 4);

        -- Get the version from the URI
        l_version := SUBSTR(uri, l_version_pos + 1,
                            l_namespace_pos - l_version_pos - 1);

        -- the version in the URI should match the version supported by
        -- Swift API
        IF version != l_version THEN
          raise_application_error(DBMS_CLOUD.EXCP_INVALID_OBJ_URI,
            'Invalid object uri - ' || request_uri);
        END IF;
      END IF;

      IF INSTR(request_uri, '/', 1, 5) != 0 AND
         INSTR(request_uri, '/', 1, 6)  = 0 THEN
        bucket_uri := request_uri;
      ELSE
        bucket_uri := SUBSTR(request_uri, 1, INSTR(request_uri, '/', 1, 6));
      END IF;

    ELSIF store_type = CSTYPE_ORACLE_BMC THEN
      -- pre-auth URL looks like below
      -- https://objectstorage.us-phoenix-1.oraclecloud.com/p/<encoded_cred>
      --   /n/<name_space>/b/<bucket_name>/o/<object_name>
      -- So the occurence of 9th '/' and a non-occurence of 10th '/' indicates
      -- that the request URI is the bucket URI itself. The request can be a
      -- PUT object request on a pre-auth bucket URL.
      -- If there is an occurence of both 9th and 10th '/', then we get the
      -- substr from the first position to the 10th '/' and use it as the
      -- bucket URI.
      IF is_par THEN
        IF INSTR(request_uri, '/', 1, 9) != 0 AND
           INSTR(request_uri, '/', 1, 10)  = 0 THEN
          bucket_uri := request_uri;
        ELSE
          bucket_uri := SUBSTR(request_uri, 1, INSTR(request_uri, '/', 1, 10));
        END IF;
      -- Regular URL looks like below
      -- https://objectstorage.us-phoenix-1.oraclecloud.com/n/<name_space>
      --   /b/<bucket_name>/o/<object_name>
      -- So the occurence of 7th '/' and a non-occurence of 8th '/' indicates
      -- that the request URI is the bucket URI itself.
      -- If there is an occurence of both 7th and 8th '/', then we get the
      -- substr from the first position to the 8th '/' and use it as the bucket
      -- URI.
      ELSE
        IF INSTR(request_uri, '/', 1, 7) != 0 AND
           INSTR(request_uri, '/', 1, 8)  = 0 THEN
          bucket_uri := request_uri;
        ELSE
          bucket_uri := SUBSTR(request_uri, 1, INSTR(request_uri, '/', 1, 8));
        END IF;
      END IF;

    ELSIF store_type = CSTYPE_MS_AZURE_BLOB THEN
      IF INSTR(request_uri, '/', 1, 3) != 0 AND
         INSTR(request_uri, '/', 1, 4) = 0
      THEN
        -- Bug 30788470: Remove query string from the URI
        bucket_uri := SUBSTR(request_uri, 1, INSTR(request_uri, '?')-1);
      ELSE
        bucket_uri := SUBSTR(request_uri, 1, INSTR(request_uri, '/', 1, 4));
      END IF;

    -- Bug 31463476: add support for Google Cloud.
    ELSIF store_type = CSTYPE_GOOGLE_CLOUD THEN
      bucket_uri := SUBSTR(request_uri, 1, INSTR(request_uri, '/', 1, 3));
      -- Bug 32306175: Set service name as s3.
      aws_service := AWS_SERVICE_S3;

    ELSIF store_type = CSTYPE_OCI_S3_COMPAT THEN
      -- Bug 31461405: If the URL is an OCI S3 compatible URL, the bucket URI
      -- should be substring which ends at the fourth '/'. If we set the region
      -- to default or leave it blank, we can only use the OCI S3 compatible
      -- URLs in Oracle Cloud Infrastructure home region. To remove this
      -- constraint, we need to parse the region correctly for OCI.
      bucket_uri := SUBSTR(request_uri, 1, INSTR(request_uri, '/', 1, 4));
      l_index    := INSTR(host_name, '.', 1, 3);
      region     := SUBSTR(host_name, l_index + 1,
                           INSTR(host_name, '.', 1, 4) - l_index - 1);
      -- Bug 32306175: Set service name as s3.
      aws_service := AWS_SERVICE_S3;

      -- Bug 32843944: region should not be empty
      IF region IS NULL THEN
        raise_application_error(DBMS_CLOUD.EXCP_UNSUPP_OBJ_STORE,
               'Unsupported object store URI - ' || uri);
      END IF;

    -- Bug 32681442: Github URL
    ELSIF store_type = 8 THEN
      bucket_uri := SUBSTR(request_uri, 1, INSTR(request_uri, 'contents/')+
                           LENGTH('contents'));

    -- BUG 32681442: Basic Authentication
    ELSIF store_type = 9 THEN
      bucket_uri := SUBSTR(request_uri, 1, INSTR(request_uri, '?'));

    -- Bug 32729117: Broker URL
    ELSIF store_type = CSTYPE_BROKER THEN
      bucket_uri := SUBSTR(request_uri, 1, INSTR(request_uri, 'v1/')+2);
    END IF;

    -- File Path
    -- Bug 31461405: If there is no credential and the original request uri has
    -- the query string delimiter (?), file_path shouldn't include the part
    -- after the delimiter.
    -- Bug 31829284: Remove checking the username.
    IF INSTR(request_uri, '?') > 0 THEN
      l_file_path_len := INSTR(request_uri, '?') - LENGTH(bucket_uri) - 1;
    ELSE
      l_file_path_len := LENGTH(request_uri) - LENGTH(bucket_uri);
    END IF;
    file_path := SUBSTR(request_uri, LENGTH(bucket_uri)+1, l_file_path_len);

  END parse_request_uri;


  -----------------------------------------------------------------------------
  -- get_current_datetime - Get Current Date/Timestamp for request header
  -----------------------------------------------------------------------------
  FUNCTION get_current_datetime
  RETURN TIMESTAMP WITH TIME ZONE
  IS
    --
    -- NAME:
    --   get_current_datetime - Get Current Date/Timestamp for request header
    --
    -- DESCRIPTION:
    --   This returns the current date/timestamp for adding to HTTP request
    --   header. This is a common header required for most Cloud Stores.
    --
    -- PARAMETERS:
    --   None
    --
    -- RETURNS:
    --   String with the current date/timestamp.
    --
    -- NOTES:
    --
  BEGIN
    RETURN CURRENT_TIMESTAMP AT TIME ZONE 'GMT';
  END get_current_datetime;


  -----------------------------------------------------------------------------
  -- get_credential_info2 - Get Information for credential object
  -----------------------------------------------------------------------------
  PROCEDURE get_credential_info2(
     credential_name       IN  VARCHAR2,
     username              OUT VARCHAR2,
     password              OUT VARCHAR2,
     key                   OUT VARCHAR2,
     switch_to_root        IN  NUMBER
  )
  IS
    --
    -- NAME:
    --   get_credential_info2 - Get info for credential object
    --
    -- DESCRIPTION:
    --   This procedure obtains the username and password from the given
    --   credential object.
    --
    -- PARAMETERS:
    --   credential_name    (IN)   - Name of the credential object
    --
    --   username           (OUT)  - Username in the credential object
    --
    --   password           (OUT)  - Password in the credential object
    --
    --   key                (OUT)  - Key attributes in the credential object
    --
    --   switch_to_root     (IN)   - flag indicates whether the credential
    --                               should be got from ROOT
    --
    LANGUAGE C
    LIBRARY sys.dbms_pdb_lib
    NAME "kpdbGetCredInfo2" WITH CONTEXT
    PARAMETERS (CONTEXT,
                credential_name  OCISTRING, credential_name  INDICATOR SB2,
                username         OCISTRING, username         INDICATOR SB2,
                password         OCISTRING, password         INDICATOR SB2,
                key              OCISTRING, key              INDICATOR SB2,
                switch_to_root   OCINUMBER);


  -----------------------------------------------------------------------------
  -- get_credential_info - Get Information for credential object
  -----------------------------------------------------------------------------
  PROCEDURE get_credential_info(
     credential_name       IN  VARCHAR2,
     username              OUT VARCHAR2,
     password              OUT VARCHAR2,
     key                   OUT VARCHAR2
  )
  IS
    --
    -- NAME:
    --   get_credential_info - Get info for credential object
    --
    -- DESCRIPTION:
    --   This procedure obtains the username and password from the given
    --   credential object.
    --
    -- PARAMETERS:
    --   credential_name    (IN)   - Name of the credential object
    --
    --   username           (OUT)  - Username in the credential object
    --
    --   password           (OUT)  - Password in the credential object
    --
    --   key                (OUT)  - Key attributes in the credential object
    --
    -- NOTES:
    --   Added for bug 25787046.
    --
    LANGUAGE C
    LIBRARY sys.dbms_pdb_lib
    NAME "kpdbGetCredInfo" WITH CONTEXT
    PARAMETERS (CONTEXT,
                credential_name  OCISTRING, credential_name  INDICATOR SB2,
                username         OCISTRING, username         INDICATOR SB2,
                password         OCISTRING, password         INDICATOR SB2,
                key              OCISTRING, key              INDICATOR SB2);


  -----------------------------------------------------------------------------
  -- init_request - Helper to initialize HTTPS Request to the Object Store
  -----------------------------------------------------------------------------
  FUNCTION init_request(
    invoker_schema   IN  VARCHAR2,
    credential_name  IN  VARCHAR2,
    base_uri         IN  VARCHAR2,
    method           IN  VARCHAR2,
    operation        IN  VARCHAR2  DEFAULT NULL,
    switch_to_root   IN  NUMBER
  ) RETURN request_context_t
  IS
    l_ctx             request_context_t;
    l_sqlstmt         DBMS_ID;
    l_wallet_loc      VARCHAR2(M_VCSIZ_4K);
    l_proxy           VARCHAR2(M_VCSIZ_4K);
    l_no_pxy_doms     VARCHAR2(M_VCSIZ_4K);
    l_domain_name     VARCHAR2(MAX_URI_LEN);
    l_credential_name VARCHAR2(M_QUAL_IDEN);
    l_is_par          BOOLEAN;
    l_is_rest_api_req BOOLEAN;
    l_request_uri     VARCHAR2(MAX_URI_LEN) := base_uri;
    l_cred_type       PLS_INTEGER := CREDTYPE_UNKNOWN;
    l_switch_to_root  NUMBER := switch_to_root;
    l_broker_request  BOOLEAN := FALSE;
  BEGIN

    --
    -- UTL_HTTP Initializations
    --
    IF REQUEST_INITIALIZED = FALSE THEN

      -- Configure Wallet for UTL_HTTP
      DBMS_CLOUD_CORE.get_db_property(PROPERTY_SSL_WALLET, l_wallet_loc, TRUE);
      UTL_HTTP.set_wallet('file:' || l_wallet_loc);

      -- Configure UTL_HTTP
      UTL_HTTP.set_detailed_excp_support(TRUE);

      -- Check if HTTP Proxy has been setup
      DBMS_CLOUD_CORE.get_db_property(PROPERTY_HTTP_PROXY, l_proxy);
      IF l_proxy IS NOT NULL THEN
        DBMS_CLOUD_CORE.get_db_property(PROPERTY_NO_PROXY_DOMS, l_no_pxy_doms);
        UTL_HTTP.set_proxy(l_proxy, l_no_pxy_doms);
      END IF;

      -- Initialization completed in the current session
      REQUEST_INITIALIZED := TRUE;

    END IF;


    -- Check for internal broker request
    IF operation IS NOT NULL AND operation = OPERATION_BROKER THEN
      l_broker_request := TRUE;
    END IF;

    -- Bug 27093538: Get qualified URI
    -- Bug 27139558: Move call to get_qualified_uri from parse_request_uri to
    --               here.
    -- Bug 32729117: Do not validate internal broker URLs
    IF NOT l_broker_request THEN
      l_request_uri := DBMS_CLOUD_CORE.get_qualified_uri(base_uri);
    END IF;

    -- Bug 29774766: Get qualified credential
    l_credential_name := get_qualified_credential(credential_name,
                                                  invoker_schema);

    --
    -- Check for cached request context
    -- Bug 32983834: If a credential name is specified, then check for cached
    -- context expiration time
    -- Bug 33305406: Always store the timestamp in American language
    --
    IF CACHED_REQUEST_CTX.EXISTS(REQUEST_CTX_METHOD) AND
       ((CACHED_REQUEST_CTX(REQUEST_CTX_CREDENTIAL) IS NULL AND
             l_credential_name IS NULL) OR
        (CACHED_REQUEST_CTX(REQUEST_CTX_CREDENTIAL) = l_credential_name AND
         CACHED_REQUEST_CTX('expiration') >
             TO_CHAR(get_current_datetime(),
                     CURRENT_DATE_FORMAT,
                     'NLS_DATE_LANGUAGE = AMERICAN'))) AND
       (CACHED_REQUEST_CTX(REQUEST_CTX_USERURI) = l_request_uri OR
        CACHED_REQUEST_CTX(REQUEST_CTX_BASEURI) = l_request_uri) AND
       CACHED_REQUEST_CTX(REQUEST_CTX_METHOD)   = method AND
       NOT l_broker_request
    THEN
      RETURN CACHED_REQUEST_CTX;
    END IF;

    --
    -- Setup the request context with request metadata
    -- Bug 29353148: Get the various components of the URI in
    -- pre-parse phase
    --
    l_ctx(REQUEST_CTX_METHOD)  := method;
    l_ctx(REQUEST_CTX_USERURI) := l_request_uri;
    pre_parse_request_uri(uri         => l_request_uri,
                          operation   => operation,
                          request_uri => l_ctx(REQUEST_CTX_BASEURI),
                          host_name   => l_ctx(REQUEST_CTX_HOSTNAME),
                          store_type  => l_ctx(REQUEST_CTX_STORETYPE),
                          version     => l_ctx(REQUEST_CTX_VERSION),
                          domain_name => l_domain_name,
                          is_par      => l_is_par
    );

    -- Bug 32983834: Set the cached request context expiration time
    l_ctx('expiration') := TO_CHAR(
                             get_current_datetime() +
                                NUMTODSINTERVAL(CACHED_REQUEST_CTX_EXPIRATION,
                                                'MINUTE'),
                             CURRENT_DATE_FORMAT,
                             'NLS_DATE_LANGUAGE = American'
                           );
    l_ctx(REQUEST_CTX_CREDENTIAL) := l_credential_name;
    l_ctx(REQUEST_CTX_ISPAR)      := CASE WHEN l_is_par THEN 'TRUE'
                                       ELSE 'FALSE' END;

    -- Get credential info only for regular URIs
    -- Bug 29774766: Credential can be null for Public URLs
    IF NOT l_is_par THEN
      IF l_credential_name IS NULL THEN
        l_credential_name := get_default_credential(invoker_schema);
      END IF;

      IF l_credential_name IS NOT NULL THEN
        BEGIN

          -- Bug 32729117: Switch to ROOT for Broker credential
          IF l_broker_request THEN
            l_switch_to_root := 1;
          END IF;

          -- Bug 32306175: If the credential is the reserved ARN credential,
          -- the credential information should be read from ROOT.
          IF l_switch_to_root IS NOT NULL AND l_switch_to_root = 1 THEN
            get_credential_info2(
                 credential_name => l_credential_name,
                 username        => l_ctx(REQUEST_CTX_USERNAME),
                 password        => l_ctx(REQUEST_CTX_PASSWORD),
                 key             => l_ctx(REQUEST_CTX_KEY),
                 switch_to_root  => l_switch_to_root
            );
          ELSE
            get_credential_info(credential_name => l_credential_name,
                                username        => l_ctx(REQUEST_CTX_USERNAME),
                                password        => l_ctx(REQUEST_CTX_PASSWORD),
                                key             => l_ctx(REQUEST_CTX_KEY)
            );
          END IF;

          -- If KEY is not an empty JSON, then the credential is key based
          IF l_ctx(REQUEST_CTX_KEY) IS NOT NULL AND
             JSON_OBJECT_T(l_ctx(REQUEST_CTX_KEY)).get_keys IS NOT NULL THEN
            l_cred_type := CREDTYPE_KEY_BASED;
          ELSE
            l_cred_type := CREDTYPE_PASSWORD_BASED;
          END IF;

        EXCEPTION
          WHEN cred_not_exist THEN
            raise_application_error(DBMS_CLOUD.EXCP_CRED_NOT_EXIST,
              'Credential ' || l_credential_name || ' does not exist');
        END;
      END IF;
    END IF;

    IF l_is_par OR l_credential_name IS NULL THEN
      l_ctx(REQUEST_CTX_USERNAME) := NULL;
      l_ctx(REQUEST_CTX_PASSWORD) := NULL;
      l_ctx(REQUEST_CTX_KEY)      := NULL;
      l_cred_type                 := CREDTYPE_UNKNOWN;
    END IF;

    -- Store the credential type in the request context
    l_ctx(REQUEST_CTX_CREDTYPE) := l_cred_type;
    l_is_rest_api_req  := rest_api_request;
    l_ctx(REQUEST_CTX_RESTAPI) := CASE WHEN l_is_rest_api_req THEN 'TRUE'
                                  ELSE 'FALSE' END;

    -- Parse request URI
    parse_request_uri(cred_type    => l_cred_type,
                      is_par       => l_is_par,
                      rest_api_req => l_is_rest_api_req,
                      domain_name  => l_domain_name,
                      uri          => l_ctx(REQUEST_CTX_USERURI),
                      request_uri  => l_ctx(REQUEST_CTX_BASEURI),
                      host_name    => l_ctx(REQUEST_CTX_HOSTNAME),
                      store_type   => l_ctx(REQUEST_CTX_STORETYPE),
                      version      => l_ctx(REQUEST_CTX_VERSION),
                      bucket_uri   => l_ctx(REQUEST_CTX_BUCKETURI),
                      file_path    => l_ctx(REQUEST_CTX_FILEPATH),
                      region       => l_ctx(REQUEST_CTX_REGION),
                      aws_service  => l_ctx(REQUEST_CTX_AWSSERVICE)
    );

    -- Cache the request context for future requests
    IF NOT l_broker_request THEN
      CACHED_REQUEST_CTX    := l_ctx;
    END IF;
    l_ctx(REQUEST_CTX_INIT) := REQUEST_CTX_INIT;

    RETURN l_ctx;

  END init_request;


  -----------------------------------------------------------------------------
  -- auth_microsoft  - Authenticate Request for Microsoft Azure Blob Storage
  -----------------------------------------------------------------------------
  PROCEDURE auth_microsoft(
        request        IN OUT UTL_HTTP.req,
        context        IN     request_context_t,
        headers        IN     JSON_OBJECT_T,
        params         IN     JSON_OBJECT_T,
        path           IN     VARCHAR2,
        use_bucket_uri IN     boolean
  )
  IS
    --
    -- NAME:
    --   auth_microsoft - Authenticate Request for Microsoft Azure Blob Storage
    --
    -- DESCRIPTION:
    --   This procedures adds the authentication headers for Microsoft Azure
    --   Blob Storage.
    --
    -- PARAMETERS:
    --   request        (IN/OUT)  - UTL_HTTP request type
    --
    --   context        (IN)      - request context
    --
    --   headers        (IN)      - HTTP headers as JSON object
    --
    --   params         (IN)      - query string parameters
    --
    --   path           (IN)      - path of the file after the base uri
    --
    --   use_bucket_uri (IN)      - use bucket URI while forming canonicalized
    --                              resource
    --
    -- NOTES:
    --
    l_str_to_sign    CLOB;
    l_header_date    DBMS_ID;
    l_account_name   DBMS_ID;
    l_access_key     VARCHAR2(M_VCSIZ_4K);
    l_signature      VARCHAR2(M_VCSIZ_4K);
    l_authorization  VARCHAR2(M_VCSIZ_4K);
    l_canon_header   VARCHAR2(M_VCSIZ_4K);
    l_canon_resource VARCHAR2(M_VCSIZ_4K);
    l_query_keys     JSON_KEY_LIST;
    l_query_key      VARCHAR2(KEY_LEN);
    l_ms_version     DBMS_ID;
    l_blob_header    DBMS_ID;
    l_headers        JSON_OBJECT_T := headers;
    l_header_keys    JSON_KEY_LIST;
    l_stmt           VARCHAR2(M_VCSIZ_4K);
  BEGIN

    -- Set the Date header
    l_header_date := TO_CHAR(get_current_datetime(), MS_DATE_FORMAT,
                             'NLS_DATE_LANGUAGE = AMERICAN') || ' GMT';
    l_headers.put(HEADER_DATE_MS, l_header_date);
    UTL_HTTP.set_header(request, HEADER_DATE_MS, l_header_date);

    -- Set the Service version header
    IF NOT l_headers.has(HEADER_VERSION_MS) OR
       l_headers.get_string(HEADER_VERSION_MS) IS NULL
    THEN
      l_ms_version := context(REQUEST_CTX_VERSION);
      l_headers.put(HEADER_VERSION_MS, l_ms_version);
      UTL_HTTP.set_header(request, HEADER_VERSION_MS,
                          l_headers.get_string(HEADER_VERSION_MS));
    END IF;


    --
    -- Check if authentication information is available
    -- Bug 30262394: PUT request with PAR URL requires canonical headers
    --
    l_account_name := context(REQUEST_CTX_USERNAME);
    l_access_key   := context(REQUEST_CTX_PASSWORD);
    IF l_account_name IS NULL OR l_access_key IS NULL THEN
      RETURN;
    END IF;


    --
    -- Construct canonicalized headers
    --
    -- NOTE:
    -- These headers need to be lexicographically ordered in ascending
    -- order.
    -- e.g x-ms-date:Sat, 21 Feb 2015 00:48:38 GMT\nx-ms-version:2014-02-14\n
    -- Add regular caonicalized headers for all kind of requests
    l_header_keys := l_headers.get_keys();
    FOR rec IN
    (
      SELECT  column_value FROM TABLE(l_header_keys)
        WHERE column_value LIKE 'x-ms-%'
        ORDER BY 1
    )
    LOOP
      l_canon_header := l_canon_header || rec.column_value || ':' ||
                        l_headers.get_string(rec.column_value) || CHR(10);
    END LOOP;


    --
    -- Construct canonicalized resource
    --
    -- NOTE:
    -- Represents the storage services resource targeted by the request. Any
    -- portion of this string derived from the resource's URI should be
    -- encoded exactly as it is in the URI.
    -- e.g
    -- 1. GET BLOB operation
    --   /myaccount/mycontainer/myblob
    -- 2. LIST BLOBS operation
    --   /myaccount/mycontainer\ncomp:list\nrestype:container
    l_canon_resource := '/' || l_account_name;

    -- Add the part of the URI that comes after the hostname to canonicalized
    -- resource string
    IF use_bucket_uri = TRUE THEN
      l_canon_resource := l_canon_resource ||
        SUBSTR(context(REQUEST_CTX_BUCKETURI),
               LENGTH('https://') + LENGTH(context(REQUEST_CTX_HOSTNAME)) + 1);
    ELSE
      l_canon_resource := l_canon_resource ||
        SUBSTR(context(REQUEST_CTX_BASEURI),
               LENGTH('https://') + LENGTH(context(REQUEST_CTX_HOSTNAME)) + 1);

      -- Bug 33147550: Put object required the URI to be complete
      -- i.e it needed to have the file path as well. But this restriction was
      -- relaxed by bug 30998372.
      -- If put_object is called with incomplete uri and a filename is passed
      -- in, we should consider the filename when we are constructing the
      -- canonicalized resource string. Otherwise it will fail with 403
      IF path IS NOT NULL THEN
        l_canon_resource := l_canon_resource || path;
      END IF;
    END IF;

    -- Loop over query string parameters and add them to the caconicalized
    -- resource.
    -- NOTE: Associative arrays already store their indexes in lexical order.
    IF params IS NOT NULL THEN
      l_query_keys := params.get_keys();
      FOR l_qidx IN 1..l_query_keys.COUNT LOOP
        l_query_key := l_query_keys(l_qidx);
        l_canon_resource := l_canon_resource || CHR(10) ||
                            l_query_key || ':' ||
                            params.get_string(l_query_key);
      END LOOP;
    END IF;


    --
    -- Construct the string to sign
    --
    -- NOTE:
    -- String to sign consists of
    -- 1. all the standard HTTP header values separated by newline (CHR(10)).
    --    These headers should lexicographically sorted.
    -- 2. Canonicalized header that is constructed above.
    -- 3. Canonicalized resource that is constructed above.
    l_str_to_sign := context(REQUEST_CTX_METHOD) || CHR(10) || -- VERB
                     CHR(10)       ||                    -- Content-Encoding
                     CHR(10);                            -- Content-Language

    IF l_headers IS NOT NULL AND
       l_headers.has(HEADER_CONTENT_LENGTH) AND
       l_headers.get_string(HEADER_CONTENT_LENGTH) != 0  -- Content-Length
    THEN
      l_str_to_sign := l_str_to_sign ||
                          l_headers.get_string(HEADER_CONTENT_LENGTH);
    END IF;
    l_str_to_sign := l_str_to_sign || CHR(10);

    l_str_to_sign := l_str_to_sign ||
                     CHR(10)       ||                    -- Content-MD5
                     CHR(10)       ||                    -- Content-Type
                     CHR(10)       ||                    -- Date
                     CHR(10)       ||                    -- If-Modified-Since
                     CHR(10)       ||                    -- If-Match
                     CHR(10)       ||                    -- If-None-Match
                     CHR(10);                            -- If-Unmodified-Since

    IF l_headers IS NOT NULL AND l_headers.has(HEADER_RANGE) THEN    -- Range
      l_str_to_sign := l_str_to_sign || l_headers.get_string(HEADER_RANGE);
    END IF;
    l_str_to_sign := l_str_to_sign || CHR(10);

    -- Add canonicalized header and canonicalized resource
    l_str_to_sign := l_str_to_sign || l_canon_header || l_canon_resource;


    --
    -- Construct the signature
    --
    -- NOTE:
    -- Call HMAC-SHA256 algorithm on the UTF-8-encoded signature string
    -- (l_str_to_sign) and encode the result as Base64. Note that we also
    -- need to Base64-decode storage account access key.
    -- Format: Base64(HMAC-SHA256(UTF8(StringToSign),
    --                            Base64.decode(<storage_account_shared_key>)))
    l_stmt := 'BEGIN                                                      ' ||
              ' :1 :=                                                     ' ||
              '  UTL_RAW.CAST_TO_VARCHAR2(                                ' ||
              '    UTL_ENCODE.BASE64_ENCODE(                              ' ||
              '      DBMS_CRYPTO.MAC(UTL_I18N.STRING_TO_RAW(:2, ''UTF8''),' ||
              '                      DBMS_CRYPTO.HMAC_SH256,              ' ||
              '                      UTL_ENCODE.BASE64_DECODE(            ' ||
              '                        UTL_RAW.CAST_TO_RAW(:3)))));       ' ||
              'END;';
    EXECUTE IMMEDIATE l_stmt
      USING OUT l_signature, IN l_str_to_sign, IN l_access_key;

    --
    -- Construct the Authorization header
    --
    l_authorization := 'SharedKey ' || l_account_name || ':' || l_signature;

    -- Set the Authorization header
    UTL_HTTP.set_header(request, HEADER_AUTHORIZATION, l_authorization);

  END auth_microsoft;


  -----------------------------------------------------------------------------
  -- auth_amazon  - Authenticate Request for Amazon S3
  -----------------------------------------------------------------------------
  PROCEDURE auth_amazon(
        request    IN OUT UTL_HTTP.req,
        context    IN     request_context_t,
        headers    IN     JSON_OBJECT_T
  )
  IS
    --
    -- NAME:
    --   auth_amazon  - Authenticate Request for Amazon S3
    --
    -- DESCRIPTION:
    --   This procedures adds the authentication headers for Amazon S3 request.
    --
    -- PARAMETERS:
    --   request    (IN/OUT)  - UTL_HTTP request type
    --
    --   context    (IN)      - request context
    --
    -- NOTES:
    --
    l_date                  TIMESTAMP WITH TIME ZONE;
    l_access_key            VARCHAR2(M_VCSIZ_4K);
    l_secret_key            VARCHAR2(M_VCSIZ_4K);
    l_scheme       CONSTANT DBMS_ID := 'AWS4-HMAC-SHA256';
    l_key                   VARCHAR2(M_VCSIZ_32K);
    l_key_obj               JSON_OBJECT_T;
    l_session_token         VARCHAR2(M_VCSIZ_4K);
    l_aws_role_arn          VARCHAR2(M_AWS_ROLE_ARN);
    l_external_id_type      VARCHAR2(M_IDEN);
  BEGIN

    l_date := get_current_datetime();               -- Current system timestamp

    -- Get the key
    l_key := context(REQUEST_CTX_KEY);

    IF l_key IS NOT NULL THEN
      l_key_obj  := JSON_OBJECT_T(l_key);

      -- If the key contains "aws_role_arn", it is an ARN based credential.
      l_aws_role_arn := TRIM(l_key_obj.get_string(REQ_PROPERTY_AWSROLEARN));
      IF l_aws_role_arn IS NOT NULL THEN
        -- Get the external id type
        l_external_id_type := TRIM(l_key_obj.
                                   get_string(REQ_PROPERTY_EXTERNALIDTYPE));
        IF l_external_id_type IS NULL THEN
          -- set the default value
          l_external_id_type := 'DATABASE_OCID';
        END IF;

        DBMS_CLOUD_CORE.assert(l_aws_role_arn IS NOT NULL,
                               'auth_amazon',
                               'aws_role_arn is not set in credential');

        IF l_aws_role_arn != ARN_CRED_CACHE.get_string(
             REQ_PROPERTY_AWSROLEARN) OR
           ARN_CRED_CACHE.get_string(TOKEN_EXPIRATION_STR) IS NULL OR
           get_current_datetime() >
               ARN_CRED_CACHE.get_string(TOKEN_EXPIRATION_STR)
        THEN
          assume_role(aws_role_arn        => l_aws_role_arn,
                      region              => context(REQUEST_CTX_REGION),
                      external_id_type    => l_external_id_type);
        END IF;

        l_access_key := ARN_CRED_CACHE.get_string(REQUEST_CTX_USERNAME);
        l_secret_key := ARN_CRED_CACHE.get_string(REQUEST_CTX_PASSWORD);
        l_session_token := ARN_CRED_CACHE.get_string(SESSION_TOKEN_STR);

        -- Bug 32306175: The security token header must be set when using
        -- temporary security credentials.
        DBMS_CLOUD_CORE.assert(l_session_token IS NOT NULL,
                               'auth_amazon', 'session token is null');
        UTL_HTTP.set_header(request, HEADER_SECURITY_TOKEN_AMZ,
                            l_session_token);

        DBMS_CLOUD_CORE.assert(l_access_key IS NOT NULL,
                               'auth_amazon', 'access key is null');
        DBMS_CLOUD_CORE.assert(l_secret_key IS NOT NULL,
                               'auth_amazon', 'secret key is null');

      -- Bug 33099613: If the key contains "gcp_pa" attribute, it is a GCP PA
      -- based credential. Set the request property "gcp_pa".
      ELSIF TRIM(l_key_obj.get_string(REQ_PROPERTY_GCPPA)) IS NOT NULL THEN
        UTL_HTTP.set_property(request, REQ_PROPERTY_GCPPA, 'gcp_pa');
      END IF;

    ELSE
      l_access_key := context(REQUEST_CTX_USERNAME);          -- AWS Access Key
      l_secret_key := context(REQUEST_CTX_PASSWORD);          -- AWS Secret Key

      DBMS_CLOUD_CORE.assert(l_access_key IS NOT NULL,
                             'auth_amazon', 'access key is null');
      DBMS_CLOUD_CORE.assert(l_secret_key IS NOT NULL,
                             'auth_amazon', 'secret key is null');

    END IF;

    -- AWS Specific headers for PUT request
    -- Bug 29128159: do not setup AWS headers if no credential passed
    -- Bug 31463476: If aws-chunked encoding is used, Google Cloud stores the
    -- resulting object with the aws-chunked encoding. Therefore, when we
    -- retrieve the object, it is aws-chunked encoded. However, aws-chunked
    -- decoding is not supported by us. Thus we remove aws-chunked encoding for
    -- submitting requests for Google Cloud's request.
    -- Bug 31461405: remove aws-chunked encoding for OCI S3 compatible URLs.
    -- Bug 31468686: If send_request is called by REST api and content-encoding
    -- is not set as "aws-chunked", send the request body in a single chunk.
    -- RTI 23446859: If the request comes form dbms_cloud package through
    -- put_object, we need to set both content-encoding and transfer-encoding
    -- so that the object can be uploaded in chunks.
    IF context(REQUEST_CTX_RESTAPI) = 'FALSE' AND
       context(REQUEST_CTX_METHOD) = DBMS_CLOUD.METHOD_PUT AND
       context(REQUEST_CTX_STORETYPE) != CSTYPE_GOOGLE_CLOUD AND
       context(REQUEST_CTX_STORETYPE) != CSTYPE_OCI_S3_COMPAT THEN
      UTL_HTTP.set_header(request, HEADER_CONTENT_ENCODING, AWS_CHUNKED);
      UTL_HTTP.set_header(request, HEADER_TRANSFER_ENCODING, 'chunked');
    END IF;

    -- Bug 31468686: If content-encoding is set as "aws-chunked" in json object
    -- "headers", set transfer-encoding as "chunked" to send the request body
    -- in multiple chunks.
    IF NOT headers.has(HEADER_TRANSFER_ENCODING) AND
       headers.has(HEADER_CONTENT_ENCODING) AND
       headers.get_string(HEADER_CONTENT_ENCODING) = AWS_CHUNKED THEN
      UTL_HTTP.set_header(request, HEADER_TRANSFER_ENCODING, 'chunked');
    END IF;

    --
    -- Set the AWS headers
    --
    UTL_HTTP.set_header(request, HEADER_DATE_AMZ,
                        TO_CHAR(l_date, 'yyyymmdd"T"hh24miss"Z"',
                        'NLS_DATE_LANGUAGE = AMERICAN'));
    UTL_HTTP.set_property(request, REQUEST_CTX_AWSSERVICE,
                          context(REQUEST_CTX_AWSSERVICE));

    --
    -- Set the Google Cloud header
    -- When requests are sent to Google Cloud, the x-amz-content-sha256 header
    -- value informs Google Cloud whether the payload is signed or not. Google
    -- Cloud can then create signature accordingly for verification. As we are
    -- transferring payload in a single chunk, we can choose not to include the
    -- payload hash so that the aws-chunked encoding can be removed for Google
    -- Cloud.
    -- Bug 31461405: set OCI S3 compatible URL header.
    --
    IF context(REQUEST_CTX_STORETYPE) = CSTYPE_GOOGLE_CLOUD OR
       context(REQUEST_CTX_STORETYPE) = CSTYPE_OCI_S3_COMPAT THEN
      UTL_HTTP.set_header(request, HEADER_CONTENT_SHA256_AMZ,
                          'UNSIGNED-PAYLOAD');
    END IF;

    --
    -- Set the AWS Region
    --
    UTL_HTTP.set_property(request, 'aws-region', context(REQUEST_CTX_REGION));

    --
    -- Set the UTL_HTTP Authentication
    --
    UTL_HTTP.set_authentication(request, l_access_key, l_secret_key, l_scheme);

  END auth_amazon;


  -----------------------------------------------------------------------------
  -- auth_oracle_bmc  - Authenticate Request for Oracle BMC object store
  -----------------------------------------------------------------------------
  PROCEDURE auth_oracle_bmc(
        request    IN OUT UTL_HTTP.req,
        context    IN     request_context_t
  )
  IS
    --
    -- NAME:
    --   auth_oracle_bmc  - Authenticate Request for Oracle BMC object store
    --
    -- DESCRIPTION:
    --   This procedures adds the authentication headers for Oracle BMC object
    --   store.
    --
    -- PARAMETERS:
    --   request    (IN/OUT)  - UTL_HTTP request type
    --
    --   context    (IN)      - request context
    --
    -- NOTES:
    --
    l_date                 TIMESTAMP WITH TIME ZONE;
    l_user_ocid            VARCHAR2(M_VCSIZ_4K);
    l_tenancy_ocid         VARCHAR2(M_VCSIZ_4K);
    l_fingerprint          VARCHAR2(M_VCSIZ_4K);
    l_private_key          VARCHAR2(M_VCSIZ_4K);
    l_rpst                 VARCHAR2(M_VCSIZ_32K);
    l_key                  VARCHAR2(M_VCSIZ_32K);
    l_key_obj              JSON_OBJECT_T;
    l_scheme      CONSTANT DBMS_ID := 'BMC';
  BEGIN

    l_date      := get_current_datetime();          -- Current system timestamp
    l_user_ocid := context(REQUEST_CTX_USERNAME);       -- Oracle BMC user ocid

    -- Get the key
    -- Bug 30828243: If key is not found, then the credential tyoe is not
    -- matching the URI type (OCI native url with non-native cred)
    l_key       := context(REQUEST_CTX_KEY);
    IF l_key IS NULL THEN
      raise_application_error(DBMS_CLOUD.EXCP_INVALID_CRED,
          'Invalid credential for the request URI - ' ||
          context(REQUEST_CTX_USERURI));
    END IF;

    -- Get the attributes of the key
    l_key_obj      := JSON_OBJECT_T(l_key);
    l_private_key  := l_key_obj.get_string('private_key');
    l_rpst         := l_key_obj.get_string('rpst');
    IF l_rpst IS NULL THEN
      l_tenancy_ocid := l_key_obj.get_string('tenancy_ocid');
      l_fingerprint  := l_key_obj.get_string('fingerprint');
      DBMS_CLOUD_CORE.assert(l_tenancy_ocid IS NOT NULL,
                             'auth_oracle_bmc', 'tenancy_ocid is null');
      DBMS_CLOUD_CORE.assert(l_fingerprint IS NOT NULL,
                             'auth_oracle_bmc', 'fingerprint is null');
    END IF;
    DBMS_CLOUD_CORE.assert(l_private_key IS NOT NULL,
                           'auth_oracle_bmc', 'private_key is null');


    --
    -- Set the Oracle BMC headers
    --
    -- Bug 33049070: TO_CHAR is nls_lang sensitive. In this case, customer's
    --               nls_lang settings was causing the below date header
    --               to contain non-textual characters, thus the final
    --               signing string constructed by nhpq was not authorized by
    --               remote webserver. To avoid these implicit conversions, we
    --               should explicitly pass the right lang settings.
    --
    UTL_HTTP.set_header(request, HEADER_DATE,
                        TO_CHAR(l_date, 'Dy, dd Mon yyyy hh24:mi:ss "GMT"',
                                'NLS_DATE_LANGUAGE = AMERICAN'));
    IF l_rpst IS NULL THEN
      UTL_HTTP.set_property(request, 'tenancy-ocid', l_tenancy_ocid);
      UTL_HTTP.set_property(request, 'user-ocid', l_user_ocid);
    ELSE
      IF l_rpst = 'NULL' THEN
        -- Clear the cached resource principal credential because it is invalid
        clear_cache;
        raise_application_error(DBMS_CLOUD.EXCP_INVALID_CRED,
          'Resource principal initialization is not complete, ' ||
          'please try again later.');
      END IF;

      -- Bug 31026371: Add ST$ prefix for rpst
      UTL_HTTP.set_property(request, 'rpst', 'ST$' || l_rpst);
    END IF;

    --
    -- Set the UTL_HTTP Authentication
    -- NOTE: For BMC auth, the fingerprint is passed as username to UTL_HTTP.
    --       For RPST auth, the username can be NULL.
    --
    UTL_HTTP.set_authentication(request, l_fingerprint, l_private_key,
                                l_scheme, TRUE);

  END auth_oracle_bmc;

  -----------------------------------------------------------------------------
  -- get_broker_token - Get Broker Token
  -----------------------------------------------------------------------------
  PROCEDURE get_broker_token(
        context        IN     request_context_t
  )
  IS
    --
    -- NAME:
    --   get_broker_token  - Get Broker Token
    --
    -- DESCRIPTION:
    --   This procedures gets broker token
    --
    -- PARAMETERS:
    --   context    (IN)  - Request context
    --
    -- NOTES:
    --   Added for bug 32729117.
    --
    l_ctx        DBMS_CLOUD_REQUEST.request_context_t;
    l_rsp        UTL_HTTP.resp;
    l_buffer     VARCHAR2(M_VCSIZ_4K);
    l_token_obj  JSON_OBJECT_T;
    l_response   CLOB;
  BEGIN

    -- Initialize Request
    l_ctx := DBMS_CLOUD_REQUEST.init_request(
                  invoker_schema  => SYS_CONTEXT('USERENV', 'CURRENT_USER'),
                  credential_name => NULL,
                  base_uri        => context(REQUEST_CTX_BUCKETURI),
                  method          => DBMS_CLOUD.METHOD_POST,
                  operation       => OPERATION_BROKER
             );
    l_rsp := DBMS_CLOUD_REQUEST.send_request(
                 context => l_ctx,
                 path    => 'token',
                 body    => UTL_RAW.cast_to_raw(
                              JSON_OBJECT(
                                'grant_type' VALUE 'password',
                                'username' VALUE context(REQUEST_CTX_USERNAME),
                                'password' VALUE context(REQUEST_CTX_PASSWORD)
                              )
                            )
             );

    BEGIN
      LOOP
        UTL_HTTP.read_text(l_rsp, l_buffer, M_VCSIZ_4K);
        l_response := l_response || l_buffer;
      END LOOP;

      DBMS_CLOUD_REQUEST.end_request(l_rsp);
    EXCEPTION
      WHEN UTL_HTTP.end_of_body THEN
        DBMS_CLOUD_REQUEST.end_request(l_rsp);
    END;

    l_token_obj := JSON_OBJECT_T.parse(l_response);
    BROKER_CRED_CACHE.put(BROKER_TOKEN,
                          l_token_obj.get_string('access_token'));
    BROKER_CRED_CACHE.put(TOKEN_EXPIRATION_STR,
                          l_token_obj.get_string('expires'));

  EXCEPTION
    WHEN OTHERS THEN
      DBMS_CLOUD_REQUEST.end_request(l_rsp);
      RAISE;
  END get_broker_token;


  -----------------------------------------------------------------------------
  -- auth_broker  - Authenticate Request for Broker
  -----------------------------------------------------------------------------
  PROCEDURE auth_broker(
        request        IN OUT UTL_HTTP.req,
        context        IN     request_context_t
  )
  IS
    --
    -- NAME:
    --   auth_broker  - Authenticate Request for Broker
    --
    -- DESCRIPTION:
    --   This procedures adds the authentication headers for Broker.
    --
    -- PARAMETERS:
    --   request    (IN/OUT)  - UTL_HTTP request type
    --
    --   context    (IN)      - request context
    --
    -- NOTES:
    --   Added for bug 32729117.
    --
    l_token_expiry  DBMS_ID;
  BEGIN

    -- Check if we have a valid broker token
    l_token_expiry := BROKER_CRED_CACHE.get_string(TOKEN_EXPIRATION_STR);
    IF l_token_expiry IS NULL OR get_current_datetime() >
              BROKER_CRED_CACHE.get_string(TOKEN_EXPIRATION_STR)
    THEN
      get_broker_token(context);
    END IF;

    -- Set the authorization header
    UTL_HTTP.set_header(request, 'Authorization', 'Bearer ' ||
                        BROKER_CRED_CACHE.get_string(BROKER_TOKEN));

  END auth_broker;


  -----------------------------------------------------------------------------
  -- auth_github  - Authenticate Request for Github
  -----------------------------------------------------------------------------
  PROCEDURE auth_github(
        request        IN OUT UTL_HTTP.req,
        context        IN     request_context_t,
        headers        IN     JSON_OBJECT_T
  )
  IS
    --
    -- NAME:
    --   auth_github  - Authenticate Request for Github
    --
    -- DESCRIPTION:
    --   This procedures adds the authentication headers for Github.
    --
    -- PARAMETERS:
    --   request    (IN/OUT)  - UTL_HTTP request type
    --
    --   context    (IN)      - request context
    --
    -- NOTES:
    --   Added for bug 32681442.
    --
  BEGIN

    -- Set the UTL_HTTP authentication
    UTL_HTTP.set_authentication(request, context(REQUEST_CTX_USERNAME),
                                context(REQUEST_CTX_PASSWORD));

    -- Set the authorization header
    IF NOT headers.has(HEADER_ACCEPT) THEN
      UTL_HTTP.set_header(request, HEADER_ACCEPT, GITHUB_ACCEPT);
    END IF;

  END auth_github;


  -----------------------------------------------------------------------------
  -- set_request_authentication - Set Authentication for the HTTP Request
  -----------------------------------------------------------------------------
  PROCEDURE set_request_authentication(
    request        IN OUT  UTL_HTTP.req,
    context        IN      request_context_t,
    headers        IN      JSON_OBJECT_T,
    params         IN      JSON_OBJECT_T,
    path           IN      VARCHAR2,
    use_bucket_uri IN      BOOLEAN
  )
  IS
    l_store_type  PLS_INTEGER;
  BEGIN

    l_store_type := context(REQUEST_CTX_STORETYPE);

    -- This procedure gets invoked even if no credential username/password,
    -- so that additional headers can be added if required (eg: Azure)
    CASE
      -- Oracle BMC ObjectStorage
      WHEN l_store_type = CSTYPE_ORACLE_BMC THEN
        IF context(REQUEST_CTX_USERNAME) IS NOT NULL THEN
          auth_oracle_bmc(request, context);
        END IF;

      -- Oracle BMC Swift Object Storage or Oracle Storage Cloud Service
      WHEN l_store_type = CSTYPE_ORACLE_BMC_SWIFT OR
           l_store_type = CSTYPE_ORACLE_OSS OR
           l_store_type = 9 THEN
        IF context(REQUEST_CTX_USERNAME) IS NOT NULL THEN
          UTL_HTTP.set_authentication(request, context(REQUEST_CTX_USERNAME),
                                      context(REQUEST_CTX_PASSWORD));
        END IF;

      -- Amazon S3
      WHEN l_store_type = CSTYPE_AMAZON_S3 OR
           l_store_type = CSTYPE_GOOGLE_CLOUD OR
           l_store_type = CSTYPE_OCI_S3_COMPAT THEN
        IF context(REQUEST_CTX_USERNAME) IS NOT NULL THEN
          auth_amazon(request, context, headers);
        END IF;

      -- Microsoft Azure Blob Storage Service
      WHEN l_store_type = CSTYPE_MS_AZURE_BLOB THEN
        auth_microsoft(request, context, headers, params,
                       path, use_bucket_uri);

      -- Github
      WHEN l_store_type = 8 THEN
        auth_github(request, context, headers);

      -- Broker authentication
      WHEN l_store_type = CSTYPE_BROKER THEN
        IF context(REQUEST_CTX_USERNAME) IS NOT NULL THEN
          auth_broker(request, context);
        END IF;

      ELSE
        DBMS_CLOUD_CORE.assert(FALSE,
                               'set_request_authentication',
                               'unknown store type ' || l_store_type);

    END CASE;

  END set_request_authentication;


  -----------------------------------------------------------------------------
  -- check_response_code - Check HTTP Response code
  -----------------------------------------------------------------------------
  PROCEDURE check_response_code(
    context        IN            request_context_t,
    resp           IN OUT NOCOPY UTL_HTTP.resp,
    object_uri     IN            VARCHAR2,
    retry          OUT           BOOLEAN,
    attempt_retry  IN            BOOLEAN DEFAULT TRUE,
    method         IN            VARCHAR2,
    use_bucket_uri IN            BOOLEAN
  )
  IS
    --
    -- NAME:
    --   check_response_code - Check HTTP Response code
    --
    -- DESCRIPTION:
    --   This procedure checks the HTTP response code and detemines if the
    --   an error status code was returned. If the status corresponds to an
    --   HTTP 4xx or 5xx code, then it signals an appropriate error.
    --
    -- PARAMETERS:
    --   context        (IN)       - request context
    --
    --   resp           (IN/OUT)   - HTTP response
    --
    --   object_uri     (IN)       - URI for the request
    --
    --   retry          (OUT)      - response indicates retry the request
    --
    --   attempt_retry  (IN)       - Attempt to check for retry codes
    --                               (default TRUE)
    --
    --   method         (IN)       -  REST method type
    --
    --   use_bucket_uri (IN)       - use bucket URI for the request
    --
    -- NOTES:
    --   Added for bug 25791817.
    --
    l_error_code  PLS_INTEGER;
    l_status_code PLS_INTEGER;
    l_error_msg   CLOB;
    l_buffer      VARCHAR2(M_VCSIZ_4K);
  BEGIN

    l_status_code := resp.status_code;
    retry         := FALSE;

    IF l_status_code < UTL_HTTP.HTTP_BAD_REQUEST THEN
      RETURN;
    END IF;

    l_error_code := -20000 - l_status_code;

    -- Handle each status code
    CASE

      -- Object not found
      WHEN l_status_code = UTL_HTTP.HTTP_NOT_FOUND THEN
        raise_application_error(l_error_code,
          'Object not found - ' || object_uri);

      -- Range Request not satisfied
      -- Bug 30702641: Due to OCI-C, treat HTTP 422 same as HTTP 416
      WHEN (method = DBMS_CLOUD.METHOD_GET AND
            (l_status_code = UTL_HTTP.HTTP_REQ_RANGE_NOT_SATISFIABLE OR
             l_status_code = 422)) THEN
        NULL;

      -- Bug 26645631: Retry request on certain errors
      -- Bug 30291020: Retry on HTTP_FORBIDDEN only for GET object requests
      WHEN (attempt_retry AND
            ((l_status_code = UTL_HTTP.HTTP_FORBIDDEN AND
              method = DBMS_CLOUD.METHOD_GET AND
              NOT use_bucket_uri) OR
             l_status_code = 429 OR
             l_status_code >= 500)) THEN
          retry := TRUE;

      -- Unauthorized request
      WHEN l_status_code = UTL_HTTP.HTTP_UNAUTHORIZED OR
           l_status_code = UTL_HTTP.HTTP_FORBIDDEN THEN
        -- Bug 32983834: clear the cached context for unauthorized requests
        clear_cache;
        raise_application_error(l_error_code,
          'Authorization failed for URI - ' || object_uri);

      ELSE -- Default
        -- Bug 30603348: Show the error response along with HTTP error code,
        -- if the response is not in HTML format. HTML response can be some
        -- generic message, so not helpful in triaging problems.
        BEGIN
          UTL_HTTP.get_header_by_name(resp, HEADER_CONTENT_TYPE, l_buffer);
          IF LENGTH(l_buffer) > 0 AND TRIM(l_buffer) != 'text/html' THEN
            l_error_msg := CHR(10) || 'Error response - ';
            LOOP
              UTL_HTTP.read_text(resp, l_buffer, M_VCSIZ_4K);
              l_error_msg := l_error_msg || l_buffer;
            END LOOP;
          END IF;
        EXCEPTION
          WHEN OTHERS THEN
            -- Ignore exceptions as we are already going to raise an exception
            NULL;
        END;
        raise_application_error(l_error_code,
            'Request failed with status HTTP ' || l_status_code || ' - ' ||
            object_uri || l_error_msg);
    END CASE;

  END check_response_code;


  -----------------------------------------------------------------------------
  -- end_request - End HTTP request
  -----------------------------------------------------------------------------
  PROCEDURE end_request(
    resp      IN OUT  UTL_HTTP.resp,
    req       IN OUT  UTL_HTTP.req,
    req_begin IN      BOOLEAN
  )
  IS
    --
    -- NAME:
    --   end_request - End request helper function
    --
    -- DESCRIPTION:
    --   This procedure is a helper function to end the HTTP request if it was
    --   started, or end the HTTP response.
    --
    --   In certain scenarios, an error can be encountered after a request is
    --   begun, but the response is not yet fetched. This function handles the
    --   cleanup of such request state.
    --
    -- PARAMETERS:
    --
    --   resp             (IN)  -  HTTP Response
    --
    --   req              (IN)  -  HTTP Request
    --
    --   req_begin        (IN)  -  Is request started
    --
    -- NOTES:
    --   Added for bug 30828243.
    --
  BEGIN

    -- End request if it was begun, otherwise end response
    IF req_begin THEN
      UTL_HTTP.end_request(req);
    ELSE
      end_request(resp);
    END IF;

  END end_request;


  -----------------------------------------------------------------------------
  -- send_request_int - Helper to send an HTTPS Request to the Object Store
  -----------------------------------------------------------------------------
  FUNCTION send_request_int(
    context        IN OUT NOCOPY request_context_t,
    path           IN            VARCHAR2        DEFAULT NULL,
    body           IN            BLOB            DEFAULT NULL,
    headers        IN            JSON_OBJECT_T   DEFAULT NULL,
    params         IN            JSON_OBJECT_T   DEFAULT NULL,
    use_bucket_uri IN            BOOLEAN         DEFAULT FALSE,
    bfile_locator  IN            BFILE           DEFAULT NULL,
    contents_ptr   IN            RAW             DEFAULT NULL,
    contents_len   IN            NUMBER          DEFAULT NULL
  ) RETURN UTL_HTTP.resp
  IS
    l_method      VARCHAR2(10);
    l_url         CLOB;
    l_user_url    CLOB := context(REQUEST_CTX_USERURI);
    l_headers     JSON_OBJECT_T;
    l_req         UTL_HTTP.req;
    l_rsp         UTL_HTTP.resp;
    l_retry       BOOLEAN;
    l_retry_cnt   PLS_INTEGER := 0;
    l_idx         NUMBER := 1;
    l_body_len    NUMBER := 0;
    l_buffer      RAW(M_VCSIZ_4K);
    l_amount      BINARY_INTEGER := M_VCSIZ_4K;
    l_bfile_loc   BFILE := bfile_locator;
    l_body_hash   VARCHAR2(M_VCSIZ_4K);
    l_err_status  PLS_INTEGER;
    l_req_begin   BOOLEAN := FALSE;
    l_stmt        VARCHAR2(M_VCSIZ_4K);
  BEGIN

    -- Initialize locals
    l_method  := UPPER(context(REQUEST_CTX_METHOD));
    l_headers := NVL(headers, JSON_OBJECT_T('{}'));
    IF use_bucket_uri = TRUE THEN
      l_url   := context(REQUEST_CTX_BUCKETURI) || path;
    ELSE
      l_url   := context(REQUEST_CTX_BASEURI) || path;
    END IF;

    IF body IS NOT NULL THEN
      l_body_len := DBMS_LOB.getlength(body);
    ELSIF l_bfile_loc IS NOT NULL THEN
      -- Get the length of the BFILE
      DBMS_LOB.open(l_bfile_loc, DBMS_LOB.LOB_READONLY);
      l_body_len := DBMS_LOB.getlength(l_bfile_loc);
      DBMS_LOB.close(l_bfile_loc);
    ELSIF contents_ptr IS NOT NULL THEN
      l_body_len := contents_len;
    END IF;

    -- Content Length header for PUT or POST requests
    IF (body IS NOT NULL OR
        l_bfile_loc IS NOT NULL OR
        contents_ptr IS NOT NULL OR
        l_method = DBMS_CLOUD.METHOD_PUT) AND
       NOT (l_headers.has(HEADER_CONTENT_LENGTH_AMZ) OR
            l_headers.has(HEADER_CONTENT_LENGTH))
    THEN
      -- Bug 30291020: For S3 Preauth URL, set standard Content-Length header
      -- Bug 31468686: If send_request is called by REST api and
      -- content-encoding is not set as "aws-chunked", set standard
      -- Content-Length header.
      IF context(REQUEST_CTX_USERNAME) IS NOT NULL AND
         context(REQUEST_CTX_STORETYPE) = CSTYPE_AMAZON_S3 AND
         (context(REQUEST_CTX_RESTAPI) = 'FALSE' OR
          (l_headers.has(HEADER_CONTENT_ENCODING) AND
           l_headers.get_string(HEADER_CONTENT_ENCODING) = AWS_CHUNKED)) THEN
        l_headers.put(HEADER_CONTENT_LENGTH_AMZ, l_body_len);
      ELSE
        l_headers.put(HEADER_CONTENT_LENGTH, l_body_len);
      END IF;
    END IF;

    -- Extra headers for POST/PUT/PATCH requests
    -- Bug 31597072: If request is made for OCI REST API (except OCI object
    -- storage), then PUT request also needs content-type and content-hash.
    -- Do not add extra headers for OCI PLSQL SDK
    -- Bug 31468686: If send_request is called by REST api and
    -- X-Amz-Decoded-Content-Length is not set, send the request body in a
    -- single chunk with a valid sha256 value.
    -- Bug 33060243: support PATCH method for REST APIs
    -- Bug 33191706: Add X-Amz-Content-SHA256 header for AWS code commit to get
    -- the SHA256 hashed body. Also use the hashed body in lowercase.
    IF body IS NOT NULL AND
       l_method IN (DBMS_CLOUD.METHOD_POST,
                    DBMS_CLOUD.METHOD_PUT,
                    DBMS_CLOUD.METHOD_PATCH) AND
       context(REQUEST_CTX_RESTAPI) = 'TRUE' AND
       (NOT l_headers.has(HEADER_CLIENT_INFO) OR
        INSTR(LOWER(l_headers.get_string(HEADER_CLIENT_INFO)),
              LOWER('Oracle-PlsqlSDK')) = 0) AND
       (l_method != DBMS_CLOUD.METHOD_PUT OR
        (context(REQUEST_CTX_STORETYPE) = CSTYPE_ORACLE_BMC AND
         REGEXP_INSTR(context(REQUEST_CTX_HOSTNAME),
                      'objectstorage\.(.*)\.oracle') = 0) OR
        (context(REQUEST_CTX_STORETYPE) = CSTYPE_AMAZON_S3 AND
         l_headers.has(HEADER_CONTENT_LENGTH)))
    THEN
      -- Content SHA256 hash
      IF NOT (l_headers.has(HEADER_CONTENT_SHA256) OR
              l_headers.has(HEADER_CONTENT_SHA256_AMZ))
      THEN
       -- Bug 31468686: For Amazon rest api requests, no need to use base 64
       -- encoding.
       IF context(REQUEST_CTX_STORETYPE) = CSTYPE_AMAZON_S3 THEN
          l_headers.put(HEADER_CONTENT_SHA256_AMZ,
                        LOWER(DBMS_CRYPTO.hash(body, DBMS_CRYPTO.HASH_SH256)));
        ELSE
          l_stmt := 'BEGIN                                            ' ||
                    '  :1 := UTL_RAW.cast_to_varchar2(                ' ||
                    '                  UTL_ENCODE.base64_encode(      ' ||
                    '                    DBMS_CRYPTO.hash(:2,         ' ||
                    '                      DBMS_CRYPTO.HASH_SH256))); ' ||
                    'END;';
          EXECUTE IMMEDIATE l_stmt USING OUT l_body_hash, IN body;
          l_headers.put(HEADER_CONTENT_SHA256, l_body_hash);
        END IF;
      END IF;

      -- Content Type
      IF NOT l_headers.has(HEADER_CONTENT_TYPE) THEN
        l_headers.put(HEADER_CONTENT_TYPE, APPLICATION_JSON);
      END IF;
    END IF;

    -- Bug 32729117: Set default headers for broker requests
    IF context(REQUEST_CTX_STORETYPE) = CSTYPE_BROKER THEN
      l_headers.put(HEADER_ACCEPT, APPLICATION_JSON);
      l_headers.put(HEADER_CONTENT_TYPE, APPLICATION_JSON);
      l_headers.put(HEADER_REQUEST_ID, DBMS_RANDOM.string('U', 32));
    END IF;

    -- Bug 32681442: Set user agent
    IF NOT l_headers.has(HEADER_USER_AGENT) THEN
      l_headers.put(HEADER_USER_AGENT, CLIENT_NAME);
    END IF;

    --
    -- Bug 26843341: Begin a new request for every retry
    --
    LOOP

      l_retry := FALSE;

      -- Begin HTTP request
      l_req := UTL_HTTP.begin_request(l_url, l_method);
      l_req_begin := TRUE;

      -- Set the headers on the request
      set_request_headers(l_req, l_headers);

      -- Set the authentication properties for the request
      -- Do not call authentication routine for Pre-Auth URIs
      -- Bug 29774766: If username is null, then do not call authentication
      set_request_authentication(l_req, context, l_headers, params, path,
                                 use_bucket_uri);

      -- Set the body content (if any)
      -- Bug 27716801: Microsoft Azure doesn't require body to create a null
      -- object in its store. But for Amazon S3, a request body is mandatory
      -- for PUT request.
      -- Bug 27817777: Use while loop, plsql for loop will overflow above ub4
      -- Bug 27849014 - Use loop..exit to ensure Amazon S3 has a request body
      IF ((body IS NOT NULL OR l_bfile_loc IS NOT NULL OR
           contents_ptr IS NOT NULL) AND
          (context(REQUEST_CTX_STORETYPE) != CSTYPE_MS_AZURE_BLOB OR
           l_body_len != 0))
      THEN
        l_idx := 1;
        -- Bug 29061158: If there is a BFILE locator passed in, read the file
        -- M_VCSIZ_4K bytes at a time and pass it to utl_http. This is more
        -- efficient than looping over the BLOB that is in memory.
        BEGIN
          IF l_bfile_loc IS NOT NULL THEN
            DBMS_LOB.open(l_bfile_loc, DBMS_LOB.LOB_READONLY);
            LOOP
              DBMS_LOB.read(l_bfile_loc, l_amount, l_idx, l_buffer);
              UTL_HTTP.write_raw(
                r    => l_req,
                data => l_buffer);
              l_idx := l_idx + l_amount;

              EXIT WHEN (l_idx > l_body_len);
            END LOOP;
            DBMS_LOB.close(l_bfile_loc);
          ELSIF contents_ptr IS NOT NULL THEN
            -- Bug 32870021: Use new UTL_HTTP API to directly read from a C
            -- code PGA buffer and write it to the request body.
            l_stmt :=  'DECLARE                       ' ||
                       '  l_req UTL_HTTP.req;         ' ||
                       'BEGIN                         ' ||
                       '  l_req.url := :1;            ' ||
                       '  l_req.method := :2;         ' ||
                       '  l_req.http_version := :3;   ' ||
                       '  l_req.private_hndl := :4;   ' ||
                       '  SYS.UTL_HTTP.write_raw_ptr( ' ||
                       '    r        => l_req,        ' ||
                       '    data_ptr => :5            ' ||
                       '  ); ' ||
                       'END;';
            EXECUTE IMMEDIATE l_stmt
                USING IN l_req.url, l_req.method, l_req.http_version,
                         l_req.private_hndl, contents_ptr;
          ELSE
            -- If it is an empty blob, then write it to the network, This
            -- is required for AWS
            IF l_body_len = 0 THEN
              UTL_HTTP.write_raw(
                r    => l_req,
                data => DBMS_LOB.substr(EMPTY_BLOB(), M_VCSIZ_4K, l_idx));
            ELSE
              LOOP
                DBMS_LOB.read(body, l_amount, l_idx, l_buffer);
                UTL_HTTP.write_raw(
                  r    => l_req,
                  data => l_buffer);
                l_idx := l_idx + l_amount;

                EXIT WHEN (l_idx > l_body_len);
              END LOOP;
            END IF;
          END IF;
        EXCEPTION
          WHEN NO_DATA_FOUND THEN
            IF l_bfile_loc IS NOT NULL THEN
              DBMS_LOB.close(l_bfile_loc);
            END IF;
        END;
      END IF;

      -- Send the request and get response
      BEGIN
        l_rsp := UTL_HTTP.get_response(l_req);
        l_req_begin := FALSE;

        -- Check if the request was successful
        check_response_code(context, l_rsp, l_user_url, l_retry, TRUE,
                            l_method, use_bucket_uri);
      EXCEPTION
        WHEN UTL_HTTP.transfer_timeout THEN
          -- Bug 27960040: Retry on HTTP timeout, until max retry attempts
          -- RTI 23492636: Raise timeout error when the maximum retry count is
          -- reached.
          IF l_retry_cnt >= MAX_REQUEST_RETRY THEN
            RAISE;
          END IF;
          l_retry := TRUE;

         WHEN UTL_TCP.end_of_input THEN
           -- RTI 23492636: retry HTTP requests which fail with ORA-29259:
           -- end-of-input reached. When end-of-input is returned, the status
           -- code is null. To avoid error "error number argument to
           -- raise_application_error of 0 is out of range", raise end-of-input
           -- error here when the maximum retry count is reached.
           IF l_retry_cnt >= MAX_REQUEST_RETRY THEN
             RAISE;
           END IF;
           l_retry := TRUE;
      END;

      -- Bug 26645631: Check for max request retry
      IF l_retry = TRUE THEN

        -- Bug 26843341: End the response before retrying
        end_request(l_rsp, l_req, l_req_begin);

        -- Check for max retry attempts
        l_retry_cnt := l_retry_cnt + 1;
        IF l_retry_cnt > MAX_REQUEST_RETRY THEN
          check_response_code(context, l_rsp, l_user_url, l_retry, FALSE,
                              l_method, use_bucket_uri);
        END IF;

        -- Take a short nap before retrying
        EXECUTE IMMEDIATE 'BEGIN DBMS_LOCK.sleep(:1); END;'
          USING POWER(2, l_retry_cnt-2);
      END IF;

      EXIT WHEN l_retry = FALSE;
    END LOOP;

    -- Bug 26743051: For GET request, if the HTTP Request Range is not
    -- satisfied, then raise end of body exception
    -- Bug 30702641: Due to Oracle OSS (OCI-C), treat HTTP 422 same as HTTP 416
    IF l_method = DBMS_CLOUD.METHOD_GET AND
       (l_rsp.status_code = UTL_HTTP.HTTP_REQ_RANGE_NOT_SATISFIABLE OR
        l_rsp.status_code = 422)
    THEN
      end_request(l_rsp, l_req, l_req_begin);
      RAISE UTL_HTTP.end_of_body;
    END IF;

    RETURN l_rsp;

  EXCEPTION
    -- Bug 30536271: Handle access denied exception with better error
    WHEN UTL_HTTP.NETWORK_ACCESS_DENIED THEN
      end_request(l_rsp, l_req, l_req_begin);
      raise_application_error(-20000 - UTL_HTTP.HTTP_UNAUTHORIZED,
          'Authorization failed for URI - ' || l_user_url);
    WHEN UTL_HTTP.HTTP_CLIENT_ERROR THEN
      end_request(l_rsp, l_req, l_req_begin);
      l_err_status := TO_NUMBER(SUBSTR(SQLERRM,
                              INSTR(SQLERRM, 'error ') + LENGTH('error '), 3));
      raise_application_error(-20000 - l_err_status,
          'Request failed with status HTTP ' || l_err_status || ' - ' ||
          l_user_url);
    WHEN OTHERS THEN
      end_request(l_rsp, l_req, l_req_begin);
      RAISE;
  END send_request_int;


  -----------------------------------------------------------------------------
  -- assume_role - create temporary credentials by assuming role via AWS STS
  -----------------------------------------------------------------------------
  PROCEDURE assume_role(
    aws_role_arn           IN  VARCHAR2,
    region                 IN  VARCHAR2,
    external_id_type       IN  VARCHAR2
  )
  IS
    --
    -- NAME:
    --   assume_role - Get AWS temporary credential
    --
    -- DESCRIPTION:
    --   By assuming the role, this function gets a set of temporary
    --   credentials that can be used to access AWS resources.
    --
    -- PARAMETERS:
    --   aws_role_arn      (IN)  - ARN of the AWS IAM role
    --
    --   region            (IN)  - AWS service region
    --
    --   external_id_type  (IN)  - Type of external ID
    --
    -- NOTES:
    --   Added by Bug 32306175.
    --
    l_url                 VARCHAR2(MAX_URI_LEN);
    l_path                VARCHAR2(MAX_URI_LEN);
    l_path_part1          VARCHAR2(MAX_URI_LEN);
    l_path_part2          VARCHAR2(MAX_URI_LEN);
    l_req                 request_context_t;
    l_http_rsp            UTL_HTTP.resp;
    l_resp_text           CLOB;
    l_buffer_raw          RAW(M_VCSIZ_4K);
    l_aws_role_arn        VARCHAR2(M_AWS_ROLE_ARN);
    l_token_expiration    VARCHAR2(M_IDEN);
    l_external_id         VARCHAR2(M_IDEN);
    l_sqlstmt             VARCHAR2(M_VCSIZ_4K);
    l_username            VARCHAR2(M_VCSIZ_4K);
    l_password            VARCHAR2(M_VCSIZ_4K);
    l_rolesessionname     VARCHAR2(M_VCSIZ_4K);
    l_session_token       CLOB;
    l_cloud_identity      VARCHAR2(M_VCSIZ_4K);
    l_cloud_identity_obj  JSON_OBJECT_T;
  BEGIN

    -- Bug 32843944: Validate that the region is of appropriate length and
    -- contains only allowed characters ([a-zA-Z0-9-]).
    IF region IS NULL OR
       LENGTH(TRIM(region)) > M_AWS_REGION OR
       TRIM(REGEXP_REPLACE(region, '[0-9a-z-]', '', 1, 0, 'in')) IS NOT NULL
    THEN
      raise_application_error(DBMS_CLOUD.EXCP_INVALID_OBJ_URI,
                              'Invalid object uri');
    END IF;

    -- Escape the string ":" and "/" in the url.
    l_sqlstmt := 'BEGIN                                  ' ||
                 '  :1 := UTL_URL.escape(:2, TRUE);      ' ||
                 'END;';
    EXECUTE IMMEDIATE l_sqlstmt USING OUT l_aws_role_arn, IN aws_role_arn;

    l_rolesessionname := SYS_CONTEXT('USERENV', 'CON_NAME') || '_' ||
                         SYS_CONTEXT('USERENV', 'SESSIONID');

    l_url := 'https://sts.' || region || '.amazonaws.com/';
    l_path_part1 := '?Action=AssumeRole' || chr(38) || 'DurationSeconds=' ||
                    TO_CHAR(AWS_MAX_SESSION_DURATION) || chr(38);
    l_path_part2 := 'RoleArn=' || l_aws_role_arn || chr(38) ||
                    'RoleSessionName=rolesession' || l_rolesessionname ||
                    chr(38) || 'Version=' || AWS_STS_VERSION;

    -- Bug 32857944: If assume_role is called by the external id validation,
    -- the external id type will be null. In this case, set no external id.
    IF external_id_type IS NOT NULL THEN
      l_sqlstmt := 'SELECT cloud_identity FROM v$pdbs WHERE con_id =:1';
      EXECUTE IMMEDIATE l_sqlstmt INTO l_cloud_identity
      USING SYS_CONTEXT('USERENV', 'CON_ID');
      l_cloud_identity_obj := JSON_OBJECT_T(l_cloud_identity);
      l_external_id := l_cloud_identity_obj.get_string(
                           UPPER(TRIM(external_id_type)));
      DBMS_CLOUD_CORE.assert(l_external_id IS NOT NULL,
                             'assume_role', 'external id is null');
      l_path := l_path_part1 || 'ExternalId=' || l_external_id ||
                chr(38) || l_path_part2;
    ELSE
      l_path := l_path_part1 || l_path_part2;
    END IF;

    -- Begin a request
    l_req := init_request(
               invoker_schema  => DBMS_ASSERT.enquote_name(
                                     SYS_CONTEXT('USERENV', 'CURRENT_USER'),
                                     FALSE),
               credential_name => AWS_ARN_CRED,
               base_uri        => l_url,
               method          => DBMS_CLOUD.METHOD_GET,
               switch_to_root  => 1
             );

    -- Bug 32843944: Ensure that the cloud type is amazon.
    IF l_req(REQUEST_CTX_STORETYPE) != CSTYPE_AMAZON_S3 THEN
      raise_application_error(DBMS_CLOUD.EXCP_INVALID_OBJ_URI,
                              'Invalid object uri');
    END IF;

    -- Send a request
    -- Bug 32903724: Call send_request_int directly to avoid the retry logic
    -- in send_request.
    BEGIN
      l_http_rsp := send_request_int(context => l_req,
                                     path    => l_path);
    EXCEPTION
      WHEN OTHERS THEN
       IF external_id_type IS NOT NULL AND SQLCODE = -20403 THEN
          -- Bug 32903724: Retry with externalid in lower case after using
          -- externalid in upper case fails.
          l_path := l_path_part1 || 'ExternalId=' || LOWER(l_external_id) ||
                    chr(38) || l_path_part2;
          l_http_rsp := send_request_int(context => l_req,
                                         path    => l_path);
        ELSE
          RAISE;
        END IF;
    END;

    -- Bug 32885332: If the request succeeds without an external id, no
    -- external id is set in the role's trust relationship. As the external id
    -- is required, raise the proper error.
    IF l_external_id IS NULL THEN
      raise_application_error(-20035,
        'External ID is not set in the Trust Relationship of AWS role');
    END IF;

    -- Get response body as text
    BEGIN
      LOOP
        UTL_HTTP.read_raw(l_http_rsp, l_buffer_raw, M_VCSIZ_4K);
        l_resp_text := l_resp_text || utl_raw.cast_to_varchar2(l_buffer_raw);
      END LOOP;

      end_request(l_http_rsp);

    EXCEPTION
      WHEN UTL_HTTP.end_of_body THEN
        -- End of response is expected exception
        end_request(l_http_rsp);
    END;

    -- Extract the temporary credentials from the XML resposne
    SELECT username, password, session_token, token_expiration
    INTO l_username, l_password, l_session_token, l_token_expiration
    FROM XMLTABLE(
      XMLNAMESPACES(DEFAULT 'https://sts.amazonaws.com/doc/2011-06-15/'),
                  'AssumeRoleResponse/AssumeRoleResult/Credentials'
    PASSING XMLTYPE(l_resp_text)
    COLUMNS username          VARCHAR2(128)  PATH 'AccessKeyId/text()',
            password          VARCHAR2(128)  PATH 'SecretAccessKey/text()',
            session_token     CLOB           PATH 'SessionToken/text()',
            token_expiration  VARCHAR2(128)  PATH 'Expiration/text()');
    l_token_expiration := TO_TIMESTAMP_TZ(l_token_expiration,
                                          CURRENT_DATE_FORMAT);

    -- Cache the credential and its token expiration time.
    ARN_CRED_CACHE.put(REQ_PROPERTY_AWSROLEARN, aws_role_arn);
    ARN_CRED_CACHE.put(REQUEST_CTX_USERNAME, l_username);
    ARN_CRED_CACHE.put(REQUEST_CTX_PASSWORD, l_password);
    ARN_CRED_CACHE.put(SESSION_TOKEN_STR,
                       SUBSTR(l_session_token, 1, M_VCSIZ_32K));
    ARN_CRED_CACHE.put(TOKEN_EXPIRATION_STR, l_token_expiration);
  END assume_role;


  -----------------------------------------------------------------------------
  -- refresh_resource_principal - Refresh resource principal token
  -----------------------------------------------------------------------------
  PROCEDURE refresh_resource_principal
  IS
    --
    -- NAME:
    --   refresh_resource_principal - Refresh resource principal token
    --
    -- DESCRIPTION:
    --   This is a helper function to refresh resource principal token in
    --   current container.
    --
    -- PARAMETERS:
    --   None.
    --
    -- NOTES:
    --   Added for bug 32729117.
    --
    l_ctx                 DBMS_CLOUD_REQUEST.request_context_t;
    l_rsp                 UTL_HTTP.resp;
    l_credential_name     DBMS_QUOTED_ID;
    l_endpoint            VARCHAR2(MAX_URI_LEN);
    l_cloud_identity      CLOB;
    l_cloud_identity_json JSON_OBJECT_T;
  BEGIN

    -- Get the token endpoint and credential
    l_endpoint        := DBMS_CLOUD_CAPABILITY.get_config_param(
                              '_rpst_endpoint');
    l_credential_name := DBMS_CLOUD_CAPABILITY.get_config_param(
                              '_rpst_credential');
    IF l_endpoint IS NULL OR l_credential_name IS NULL THEN
      RETURN;
    END IF;

    -- Add tenant and database ocid in the endpoint
    EXECUTE IMMEDIATE 'SELECT cloud_identity FROM v$pdbs'
        INTO l_cloud_identity;
    IF l_cloud_identity IS NULL THEN
      RETURN;
    END IF;
    l_cloud_identity_json := JSON_OBJECT_T.parse(l_cloud_identity);
    l_endpoint := REPLACE(l_endpoint, '<tenant_ocid>',
                          l_cloud_identity_json.get_string('TENANT_OCID'));
    l_endpoint := REPLACE(l_endpoint, '<database_name>',
                          l_cloud_identity_json.get_string('DATABASE_NAME'));

    -- Initialize Reqnuest
    l_ctx := DBMS_CLOUD_REQUEST.init_request(
                  invoker_schema  => SYS_CONTEXT('USERENV', 'CURRENT_USER'),
                  credential_name => l_credential_name,
                  base_uri        => l_endpoint,
                  method          => DBMS_CLOUD.METHOD_PUT,
                  operation       => OPERATION_BROKER
             );
    l_rsp := DBMS_CLOUD_REQUEST.send_request(context => l_ctx);
    DBMS_CLOUD_REQUEST.end_request(l_rsp);

    -- Wait for token to updated asynchronously by broker.
    EXECUTE IMMEDIATE 'BEGIN DBMS_LOCK.sleep(:1); END;' USING 3;

  EXCEPTION
    WHEN OTHERS THEN
      -- End the response to avoid leaking HTTP connection
      DBMS_CLOUD_REQUEST.end_request(l_rsp);
      -- Do not raise any exceptions
  END refresh_resource_principal;



  -----------------------------------------------------------------------------
  --                        PUBLIC FUNCTIONS/PROCEDURES
  -----------------------------------------------------------------------------

  -----------------------------------------------------------------------------
  -- init_request - Initialize HTTPS Request to the Object Store
  -----------------------------------------------------------------------------
  FUNCTION init_request(
    invoker_schema   IN  VARCHAR2,
    credential_name  IN  VARCHAR2,
    base_uri         IN  VARCHAR2,
    method           IN  VARCHAR2,
    operation        IN  VARCHAR2  DEFAULT NULL
  ) RETURN request_context_t
  IS
  BEGIN

    -- Initialize the request
    RETURN init_request(invoker_schema  => invoker_schema,
                        credential_name => credential_name,
                        base_uri        => base_uri,
                        method          => method,
                        operation       => operation,
                        switch_to_root  => 0);

  END init_request;


  -----------------------------------------------------------------------------
  -- send_request - Send HTTPS Request
  -----------------------------------------------------------------------------
  FUNCTION send_request(
    context        IN OUT NOCOPY request_context_t,
    path           IN            VARCHAR2            DEFAULT NULL,
    body           IN            BLOB                DEFAULT NULL,
    headers        IN            JSON_OBJECT_T       DEFAULT NULL,
    params         IN            JSON_OBJECT_T       DEFAULT NULL,
    use_bucket_uri IN            BOOLEAN             DEFAULT FALSE,
    bfile_locator  IN            BFILE               DEFAULT NULL,
    contents_ptr   IN            RAW                 DEFAULT NULL,
    contents_len   IN            NUMBER              DEFAULT NULL
  ) RETURN UTL_HTTP.resp
  IS
    l_username    VARCHAR2(M_VCSIZ_4K);
    l_err_status  PLS_INTEGER;
  BEGIN

    -- Send the request
    RETURN send_request_int(context, path, body, headers, params,
                            use_bucket_uri, bfile_locator, contents_ptr,
                            contents_len);

  EXCEPTION
    WHEN UTL_HTTP.end_of_body THEN
      RAISE;

    WHEN UTL_HTTP.HTTP_SERVER_ERROR THEN
      -- Bug 31213993: If the HTTP request encounters an HTTP server error
      -- (status code 5xx), then we should raise the error instead of retrying
      -- the request.
      l_err_status := TO_NUMBER(SUBSTR(SQLERRM,
                              INSTR(SQLERRM, 'error ') + LENGTH('error '), 3));
      raise_application_error(-20000 - l_err_status,
        'Request failed with status HTTP ' || l_err_status || ' - ' ||
        context(REQUEST_CTX_USERURI));

    WHEN OTHERS THEN
      -- Check if we used default credential in first attempt for non-PAR URL,
      -- and then retry without any credential
      -- Bug 31762202: When PAR and regular URLs both exist and a credential is
      -- prodivded, the PAR URL requests will fail and are not retried with
      -- non-credential. Add a condition so that URLs will be retried if PAR
      -- URL requests fail with the credential.
      -- Bug 31935490: If the call stack has DBMS_CLOUD.SEND_REQUEST, we do not
      -- need to check whether the base url is a PAR url or not.
      -- Bug 32681442: Do not retry without credential for Github requests
      IF context(REQUEST_CTX_ISPAR) = 'FALSE' AND
         context(REQUEST_CTX_STORETYPE) != 8 AND
         (context(REQUEST_CTX_CREDENTIAL) IS NULL OR
          (context(REQUEST_CTX_RESTAPI) = 'FALSE' AND
           INSTR(context(REQUEST_CTX_BASEURI), '?') > 0)) THEN

        IF context(REQUEST_CTX_USERNAME) IS NOT NULL THEN
          BEGIN
            -- Save the request username
            l_username := context(REQUEST_CTX_USERNAME);

            -- Resend the request without username (no auth)
            context(REQUEST_CTX_USERNAME) := NULL;
            RETURN send_request_int(context, path, body, headers, params,
                                    use_bucket_uri, bfile_locator,
                                    contents_ptr, contents_len);
          EXCEPTION
            -- Bug 30191483: end of body means the URL was access successfully
            WHEN UTL_HTTP.end_of_body THEN
              RAISE;

            WHEN OTHERS THEN
              -- Restore the request username and then raise error
              context(REQUEST_CTX_USERNAME) := l_username;
          END;
        END IF;

        -- Bug 29774766: If we reached here, then no credential was passed and
        -- there is an error encountered in the request. So raise missing
        -- credential error in this case.
        raise_application_error(DBMS_CLOUD.EXCP_INVALID_CRED,
              'Missing credential name');
      ELSE
        RAISE;
      END IF;
  END send_request;


  -----------------------------------------------------------------------------
  -- send_request - Send HTTPS Request
  -- NOTE: Will be removed once spec changes for send_request with
  --       contents_ptr and contents_len fields are merged
  -----------------------------------------------------------------------------
  FUNCTION send_request(
    context        IN OUT NOCOPY request_context_t,
    path           IN            VARCHAR2            DEFAULT NULL,
    body           IN            BLOB                DEFAULT NULL,
    headers        IN            JSON_OBJECT_T       DEFAULT NULL,
    params         IN            JSON_OBJECT_T       DEFAULT NULL,
    use_bucket_uri IN            BOOLEAN             DEFAULT FALSE,
    bfile_locator  IN            BFILE               DEFAULT NULL
  ) RETURN UTL_HTTP.resp
  IS
  BEGIN

    RETURN send_request(context        => context,
                        path           => path,
                        body           => body,
                        headers        => headers,
                        params         => params,
                        use_bucket_uri => use_bucket_uri,
                        bfile_locator  => bfile_locator,
                        contents_ptr   => NULL,
                        contents_len   => NULL
                       );

  END send_request;


  -----------------------------------------------------------------------------
  -- end_request - End HTTP request
  -----------------------------------------------------------------------------
  PROCEDURE end_request(
    resp    IN OUT  UTL_HTTP.resp
  )
  IS
  BEGIN

    IF resp.private_hndl IS NOT NULL THEN
      UTL_HTTP.end_response(resp);
    END IF;

  END end_request;


  -----------------------------------------------------------------------------
  -- get_cloud_store_type - Get cloud store type for a request
  -----------------------------------------------------------------------------
  FUNCTION get_cloud_store_type(
    context    IN  request_context_t
  ) RETURN PLS_INTEGER
  IS
  BEGIN
    RETURN context(REQUEST_CTX_STORETYPE);
  END get_cloud_store_type;


  -----------------------------------------------------------------------------
  -- get_uri_file_type  -  Get the file path from request uri
  -----------------------------------------------------------------------------
  FUNCTION get_uri_file_path(
    context    IN  request_context_t
  ) RETURN VARCHAR2
  IS
  BEGIN
    RETURN context(REQUEST_CTX_FILEPATH);
  END get_uri_file_path;


  -----------------------------------------------------------------------------
  -- clear_cache - clear cached credentials in request cache
  -----------------------------------------------------------------------------
  PROCEDURE clear_cache
  IS
  BEGIN
    IF CACHED_REQUEST_CTX.EXISTS(REQUEST_CTX_METHOD) THEN
      CACHED_REQUEST_CTX.DELETE(REQUEST_CTX_METHOD);
    END IF;
  END clear_cache;


END dbms_cloud_request;  -- End of DBMS_CLOUD_REQUEST Package
/
